Ignore:
Timestamp:
Nov 8, 2013, 2:28:14 PM (8 years ago)
Author:
Nicklas Nordborg
Message:

References #489: Histology scoring wizard

Implemented saving of scores. There are still some rough corners and error handling to take care of.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • extensions/net.sf.basedb.reggie/branches/ticket-489/resources/sampleproc/histology_score.jsp

    r2124 r2125  
    3535
    3636<script language="JavaScript">
    37 var debug = 1;
     37var debug = 0;
    3838var currentStep = 1;
    3939var selectedSample;
    4040var heGlass;
     41
     42var SCORES = ['ScoreInvasiveCancer', 'ScoreInsituCancer', 'ScoreLymphocytes', 'ScoreNormal', 'ScoreStroma', 'ScoreFat'];
     43var FIELDS = ['score_invasive_cancer', 'score_insitu_cancer', 'score_lymphocytes', 'score_normal', 'score_stroma', 'score_fat'];
    4144
    4245function init()
     
    6871        var sample = glass.samples[sampleNo];
    6972       
     73        saveOriginalProperties(sample);
     74       
    7075        // Save ID of HE glass since we need that later
    7176        sample.heGlassId = glass.id;
     
    7883       
    7984        // Calculate sum of scores
    80         var total = calculateTotalScore(sample)
     85        var total = calculateTotalScore(sample);
    8186       
    8287        // Generate text inside table cell
    83         var html = sample.name.replace(/\.he\d+$/, '');
     88        var html = '<div class="sample-name">'+sample.name.replace(/\.he\d+$/, '')+'</div>';
    8489        html += '<div class="progress" id="'+sample.id+'.progress">' + total + '%</div>';
    8590       
     
    95100    if (firstGoodSample != null)
    96101    {
     102      Main.show('score-container');
    97103      selectSample(firstGoodSample);
    98       Main.show('score-container');
    99104    }
    100105    Main.show('gocreate');
     106    window.addEventListener('beforeunload', confirmIfModified);
     107
    101108  }
    102109  else
     
    118125  // Wait a second until loading uncompleted HE glass
    119126  setTimeout(loadUncompletedHeGlass, 1000);
    120 }
    121 
    122 function findHeGlass(glassName)
     127 
     128}
     129
     130/**
     131  Remember the original properties so that we know if
     132  a sample has been modified.
     133*/
     134function saveOriginalProperties(sample)
     135{
     136  var o = {};
     137  for (var i = 0; i < SCORES.length; i++)
     138  {
     139    var p = SCORES[i];
     140    o[p] = sample[p];
     141  }
     142 
     143  o.GoodStain = sample.GoodStain;
     144  o.ScoreComplete = sample.ScoreComplete;
     145  sample.o = o;
     146  sample.isModified = false;
     147}
     148
     149/**
     150  Check if a sample has been modified.
     151*/
     152function isModified(sample, useCache)
     153{
     154  if (useCache)
     155  {
     156    return sample.isModified;
     157  }
     158  var o = sample.o;
     159  for (var p in o)
     160  {
     161    if (o[p] != sample[p]) return true;
     162  }
     163  return false;
     164}
     165
     166function findHeGlass(glassName, autoFind)
    123167{
    124168  var frm = document.forms['reggie'];
    125   if (!glassName)
     169  if (!glassName && autoFind != true)
    126170  {
    127171    glassName = frm.findGlassName.value;
     
    135179    }
    136180  }
    137   location.href = '<%=home%>/sampleproc/histology_score.jsp?ID=<%=ID%>&name='+encodeURIComponent(glassName);
     181  var url =  '<%=home%>/sampleproc/histology_score.jsp?ID=<%=ID%>';
     182  if (glassName)
     183  {
     184    url += '&name='+encodeURIComponent(glassName);
     185  }
     186 
     187  location.href = url;
    138188}
    139189
     
    224274  }
    225275 
    226   if (sample.ScoreTotal == 0)
     276  if (sample.ScoreTotal == null)
    227277  {
    228278    className += ' score-none';
     
    235285  {
    236286    className += ' score-complete';
     287  }
     288 
     289  if (isModified(sample))
     290  {
     291    className += ' modified';
    237292  }
    238293 
     
    254309 
    255310  // Scores
    256   frm.score_invasive_cancer.value = sample.ScoreInvasiveCancer;
    257   frm.score_insitu_cancer.value = sample.ScoreInsituCancer;
    258   frm.score_lymphocytes.value = sample.ScoreLymphocytes;
    259   frm.score_normal.value = sample.ScoreNormal;
    260   frm.score_stroma.value = sample.ScoreStroma;
    261   frm.score_fat.value = sample.ScoreFat;
     311  for (var i = 0; i < SCORES.length; i++)
     312  {
     313    frm[FIELDS[i]].value = sample[SCORES[i]];
     314  }
    262315 
    263316  // Total score + score complete
     
    266319  if (total == 100)
    267320  {
    268     frm.score_complete.disabled = false;
    269     frm.score_complete.checked = sample.ScoreComplete;
    270     setInputStatus('score_total', '', 'valid');
     321    if (sample.numNullScores == 0)
     322    {
     323      frm.score_complete.disabled = false;
     324      frm.score_complete.checked = sample.ScoreComplete;
     325      setInputStatus('score_total', '', 'valid');
     326    }
     327    else
     328    {
     329      frm.score_complete.disabled = true;
     330      frm.score_complete.checked = false;
     331      setInputStatus('score_total', 'Enter 0 in empty fields to complete scoring', 'warning');
     332    }
    271333  }
    272334  else
     
    315377  var frm = document.forms['reggie'];
    316378
    317   selectedSample.ScoreInvasiveCancer = toScore(frm.score_invasive_cancer.value);
    318   selectedSample.ScoreInsituCancer = toScore(frm.score_insitu_cancer.value);
    319   selectedSample.ScoreLymphocytes = toScore(frm.score_lymphocytes.value);
    320   selectedSample.ScoreNormal = toScore(frm.score_normal.value);
    321   selectedSample.ScoreStroma = toScore(frm.score_stroma.value);
    322   selectedSample.ScoreFat = toScore(frm.score_fat.value);
     379  for (var i = 0; i < SCORES.length; i++)
     380  {
     381    selectedSample[SCORES[i]] = toScore(frm[FIELDS[i]].value);
     382  }
    323383 
    324384  var oldTotal = selectedSample.ScoreTotal;
     385  var oldNumNull = selectedSample.numNullScores;
     386 
    325387  var total = calculateTotalScore(selectedSample);
    326388 
     
    330392  if (total == 100)
    331393  {
    332     frm.score_complete.disabled = false;
    333     if (oldTotal != total)
    334     {
    335       // Only check the 'Score complete' if value has changed
    336       frm.score_complete.checked = true;
    337       selectedSample.ScoreComplete = true;
    338     }
    339     setInputStatus('score_total', '', 'valid');
     394    if (selectedSample.numNullScores == 0)
     395    {
     396      // All scores have been entered
     397      frm.score_complete.disabled = false;
     398      if (oldTotal != total || oldNumNull != selectedSample.numNullScores)
     399      {
     400        // Only check the 'Score complete' if value has changed
     401        frm.score_complete.checked = true;
     402        selectedSample.ScoreComplete = true;
     403      }
     404      setInputStatus('score_total', '', 'valid');
     405    }
     406    else
     407    {
     408      frm.score_complete.disabled = true;
     409      frm.score_complete.checked = false;
     410      selectedSample.ScoreComplete = false;
     411      setInputStatus('score_total', 'Enter 0 in empty fields to complete scoring', 'warning');
     412    }
    340413  }
    341414  else
     
    343416    frm.score_complete.disabled = true;
    344417    frm.score_complete.checked = false;
    345     selectedSample.ScoreComplete = false;
    346     setInputStatus('score_total', 'Not 100%', 'warning');
     418   
     419    if (selectedSample.ScoreTotal != null)
     420    {
     421      selectedSample.ScoreComplete = false;
     422      setInputStatus('score_total', 'Not 100%', 'warning');
     423    }
     424    else
     425    {
     426      selectedSample.ScoreComplete = null;
     427      setInputStatus('score_total', '', '');
     428    }
    347429  }
    348430
     
    352434function calculateTotalScore(sample)
    353435{
    354   var total = sumScore(sample.ScoreInvasiveCancer);
    355   total += sumScore(sample.ScoreInsituCancer);
    356   total += sumScore(sample.ScoreLymphocytes);
    357   total += sumScore(sample.ScoreNormal);
    358   total += sumScore(sample.ScoreStroma);
    359   total += sumScore(sample.ScoreFat);
    360   sample.ScoreTotal = total;
     436  var total = 0;
     437  var numNull = 0;
     438  for (var i = 0; i < SCORES.length; i++)
     439  {
     440    var score = sample[SCORES[i]];
     441    total += sumScore(score);
     442    if (score == null) numNull++;
     443  }
     444  // Set summed total, null only if all values are null
     445  sample.ScoreTotal = total == 0 && numNull == SCORES.length ? null : total;
     446  sample.numNullScores = numNull;
    361447  return total;
    362448}
     
    394480      if (sample.well.location == selectedSample.well.location)
    395481      {
    396         sample.GoodStain = false;
    397         var td = document.getElementById(glass.id+'-'+sample.well.location);
    398         Main.removeClass(td, 'good-stain');
     482        sample.GoodStain = null;
     483        setSampleClassName(sample);
    399484      }
    400485    }
    401486  }
    402487 
    403   selectedSample.GoodStain = frm.good_stain.checked;
     488  selectedSample.GoodStain = frm.good_stain.checked ? true : null;
    404489  frm.good_stain.disabled = true;
    405490  setSampleClassName(selectedSample);
     
    423508  }
    424509  finally
    425   {
    426     //hideLoadingAnimation();
    427   }
     510  {}
    428511 
    429512  if (debug) Main.debug(request.responseText);
     
    443526     
    444527        html += '<div class="menuitem enabled" id="glass-'+glass.id+'">';
    445         html += Main.encodeTags(glass.name)+'</div>';
     528        html += Main.encodeTags(glass.name)+' ('+glass.numUncompleteSamples +')</div>';
    446529      }
    447530      setInnerHTML('select-uncompleted-heglass-menu', html);
     
    473556  findHeGlass(event.target.innerHTML);
    474557}
     558
     559/**
     560  Confirm if leaving the page is ok. If no event is passed this method
     561  is called manually and need to display dialog, otherwise the browser
     562  is taking care of this automatically.
     563*/
     564function confirmIfModified(event)
     565{
     566  var numModified = 0;
     567  for (var glassNo = 0; glassNo < heGlass.length; glassNo++)
     568  {
     569    var glass = heGlass[glassNo];
     570    for (var sampleNo = 0; sampleNo < glass.samples.length; sampleNo++)
     571    {
     572      var sample = glass.samples[sampleNo];
     573      if (isModified(sample))
     574      {
     575        numModified++;
     576      }
     577    }
     578  }
     579 
     580  var okToContinue = true;
     581  if (numModified > 0)
     582  {
     583    if (event)
     584    {
     585      event.returnValue = 'There are unsaved modifications. Continue?';
     586    }
     587    else
     588    {
     589      if (!confirm('There are unsaved modifications. Continue?'))
     590      {
     591        okToContinue = false;
     592      }
     593    }
     594  }
     595 
     596  return okToContinue;
     597}
     598
     599function saveModified()
     600{
     601  var samples = [];
     602  var glassInfo = [];
     603 
     604  for (var glassNo = 0; glassNo < heGlass.length; glassNo++)
     605  {
     606    var glass = heGlass[glassNo];
     607    var scoreCompleteForGlass = true;
     608    for (var sampleNo = 0; sampleNo < glass.samples.length; sampleNo++)
     609    {
     610      var sample = glass.samples[sampleNo];
     611      if (isModified(sample))
     612      {
     613        samples[samples.length] = sample;
     614      }
     615     
     616      if (sample.GoodStain && sample.ScoreComplete != true || sample.ScoreComplete == false)
     617      {
     618        scoreCompleteForGlass = false;
     619      }
     620    }
     621   
     622    var tmp = {};
     623    tmp.id = glass.id;
     624    tmp.ScoreComplete = scoreCompleteForGlass;
     625    glassInfo[glassInfo.length] = tmp;
     626  }
     627 
     628  if (samples.length == 0)
     629  {
     630    if (!confirm('Nothing has changed! Continue saving?'))
     631    {
     632      return;
     633    }
     634  }
     635 
     636  Main.hide('score-container');
     637  Main.addClass(document.getElementById('he-glass-container'), 'disabled');
     638   
     639  var submitInfo = {};
     640  submitInfo.samples = samples;
     641  submitInfo.heGlass = glassInfo;
     642 
     643  if (debug) Main.debug(JSON.stringify(submitInfo));     
     644  var request = Ajax.getXmlHttpRequest();
     645 
     646  try
     647  {
     648    showLoadingAnimation('Saving...');
     649    var url = '../Histology.servlet?ID=<%=ID%>&cmd=SaveHeScore';
     650    request.open("POST", url, false);
     651    request.setRequestHeader("Content-Type", "application/json");
     652    request.send(JSON.stringify(submitInfo));
     653  }
     654  finally
     655  {
     656    hideLoadingAnimation();
     657  }
     658
     659  if (debug) Main.debug(request.responseText);
     660
     661
     662  var response = JSON.parse(request.responseText);
     663  if (response.status != 'ok')
     664  {
     665    setFatalError(response.message);
     666    return false;
     667  }
     668 
     669  var msg = '<ul>';
     670  for (var i = 0; i < response.messages.length; i++)
     671  {
     672    msg += '<li>' + response.messages[i];
     673  }
     674  msg += '</ul>';
     675  setInnerHTML('done', msg);
     676 
     677  Main.show('done');
     678  Main.show('gonext');
     679  Main.hide('gocreate');
     680
     681}
     682
    475683</script>
    476684<style>
     
    585793{
    586794  background-color: #D8FFFF;
    587   border: 2px solid #2288AA;
    588   border-radius: 4px;
    589   padding: 1px;
    590795}
    591796
     
    609814{
    610815  background-image: url('../images/score-complete.png');
     816}
     817
     818td.modified .sample-name:after
     819{
     820  content: ' *';
     821  color: #2244FF;
    611822}
    612823
     
    686897  %>
    687898 
    688   <div id="select-uncompleted-heglass" class="menu vertical" style="width: 150px; display: none;"
     899  <div id="select-uncompleted-heglass" class="menu vertical" style="width: 150px; display: none; z-index: 999;"
    689900    onclick="uncompletedGlassSelected(event)">
    690901    <div id="select-uncompleted-heglass-menu"></div>
     
    693904  <form name="reggie" onsubmit="return false;">
    694905 
    695   <table class="stepform">
     906  <table class="stepform" id="step.1.section">
    696907  <tr>
    697908    <td rowspan="3" class="stepno">1</td>
     
    8271038            title="Uncheck if scoring is not complete"></span></td>
    8281039          <td class="status" id="score_complete.status"></td>
    829           <td class="help">Uncheck
    830             if scoring is not complete. Disabled if the total score is not 100%.
     1040          <td class="help">
     1041            Uncheck if scoring is not complete. Disabled if some scores
     1042            are missing or the total score is not 100%.
    8311043          </td>
    8321044        </tr>
     
    8551067    <tr>
    8561068      <td><base:button id="gocancel" title="Cancel" onclick="goRestart(false)" style="display: none;" /></td>
    857       <td><base:button id="gonext" title="Next" image="<%=home+"/images/gonext.png"%>" onclick="goNext(true)" style="display: none;" /></td>
    858       <td><base:button id="gocreate" title="Save" image="<%=home+"/images/gonext.png"%>" onclick="goCreate()" style="display: none;" /></td>
     1069      <td><base:button id="gonext" title="Next HE glass set" image="<%=home+"/images/gonext.png"%>" onclick="findHeGlass(null, true)" style="display: none;" /></td>
     1070      <td><base:button id="gocreate" title="Save" image="<%=home+"/images/gonext.png"%>" onclick="saveModified()" style="display: none;" /></td>
    8591071      <td><base:button id="gorestart" title="Restart" image="<%=home+"/images/goback.png"%>" onclick="goRestart(true)" style="display: none;"/></td>
    8601072      <td id="gonext.message" class="message"></td>
Note: See TracChangeset for help on using the changeset viewer.