Changeset 2125
- Timestamp:
- Nov 8, 2013, 2:28:14 PM (9 years ago)
- Location:
- extensions/net.sf.basedb.reggie/branches/ticket-489
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
extensions/net.sf.basedb.reggie/branches/ticket-489/resources/sampleproc/histology_score.jsp
r2124 r2125 35 35 36 36 <script language="JavaScript"> 37 var debug = 1;37 var debug = 0; 38 38 var currentStep = 1; 39 39 var selectedSample; 40 40 var heGlass; 41 42 var SCORES = ['ScoreInvasiveCancer', 'ScoreInsituCancer', 'ScoreLymphocytes', 'ScoreNormal', 'ScoreStroma', 'ScoreFat']; 43 var FIELDS = ['score_invasive_cancer', 'score_insitu_cancer', 'score_lymphocytes', 'score_normal', 'score_stroma', 'score_fat']; 41 44 42 45 function init() … … 68 71 var sample = glass.samples[sampleNo]; 69 72 73 saveOriginalProperties(sample); 74 70 75 // Save ID of HE glass since we need that later 71 76 sample.heGlassId = glass.id; … … 78 83 79 84 // Calculate sum of scores 80 var total = calculateTotalScore(sample) 85 var total = calculateTotalScore(sample); 81 86 82 87 // 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>'; 84 89 html += '<div class="progress" id="'+sample.id+'.progress">' + total + '%</div>'; 85 90 … … 95 100 if (firstGoodSample != null) 96 101 { 102 Main.show('score-container'); 97 103 selectSample(firstGoodSample); 98 Main.show('score-container');99 104 } 100 105 Main.show('gocreate'); 106 window.addEventListener('beforeunload', confirmIfModified); 107 101 108 } 102 109 else … … 118 125 // Wait a second until loading uncompleted HE glass 119 126 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 */ 134 function 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 */ 152 function 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 166 function findHeGlass(glassName, autoFind) 123 167 { 124 168 var frm = document.forms['reggie']; 125 if (!glassName )169 if (!glassName && autoFind != true) 126 170 { 127 171 glassName = frm.findGlassName.value; … … 135 179 } 136 180 } 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; 138 188 } 139 189 … … 224 274 } 225 275 226 if (sample.ScoreTotal == 0)276 if (sample.ScoreTotal == null) 227 277 { 228 278 className += ' score-none'; … … 235 285 { 236 286 className += ' score-complete'; 287 } 288 289 if (isModified(sample)) 290 { 291 className += ' modified'; 237 292 } 238 293 … … 254 309 255 310 // 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 } 262 315 263 316 // Total score + score complete … … 266 319 if (total == 100) 267 320 { 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 } 271 333 } 272 334 else … … 315 377 var frm = document.forms['reggie']; 316 378 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 } 323 383 324 384 var oldTotal = selectedSample.ScoreTotal; 385 var oldNumNull = selectedSample.numNullScores; 386 325 387 var total = calculateTotalScore(selectedSample); 326 388 … … 330 392 if (total == 100) 331 393 { 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 } 340 413 } 341 414 else … … 343 416 frm.score_complete.disabled = true; 344 417 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 } 347 429 } 348 430 … … 352 434 function calculateTotalScore(sample) 353 435 { 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; 361 447 return total; 362 448 } … … 394 480 if (sample.well.location == selectedSample.well.location) 395 481 { 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); 399 484 } 400 485 } 401 486 } 402 487 403 selectedSample.GoodStain = frm.good_stain.checked ;488 selectedSample.GoodStain = frm.good_stain.checked ? true : null; 404 489 frm.good_stain.disabled = true; 405 490 setSampleClassName(selectedSample); … … 423 508 } 424 509 finally 425 { 426 //hideLoadingAnimation(); 427 } 510 {} 428 511 429 512 if (debug) Main.debug(request.responseText); … … 443 526 444 527 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>'; 446 529 } 447 530 setInnerHTML('select-uncompleted-heglass-menu', html); … … 473 556 findHeGlass(event.target.innerHTML); 474 557 } 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 */ 564 function 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 599 function 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 475 683 </script> 476 684 <style> … … 585 793 { 586 794 background-color: #D8FFFF; 587 border: 2px solid #2288AA;588 border-radius: 4px;589 padding: 1px;590 795 } 591 796 … … 609 814 { 610 815 background-image: url('../images/score-complete.png'); 816 } 817 818 td.modified .sample-name:after 819 { 820 content: ' *'; 821 color: #2244FF; 611 822 } 612 823 … … 686 897 %> 687 898 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;" 689 900 onclick="uncompletedGlassSelected(event)"> 690 901 <div id="select-uncompleted-heglass-menu"></div> … … 693 904 <form name="reggie" onsubmit="return false;"> 694 905 695 <table class="stepform" >906 <table class="stepform" id="step.1.section"> 696 907 <tr> 697 908 <td rowspan="3" class="stepno">1</td> … … 827 1038 title="Uncheck if scoring is not complete"></span></td> 828 1039 <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%. 831 1043 </td> 832 1044 </tr> … … 855 1067 <tr> 856 1068 <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> 859 1071 <td><base:button id="gorestart" title="Restart" image="<%=home+"/images/goback.png"%>" onclick="goRestart(true)" style="display: none;"/></td> 860 1072 <td id="gonext.message" class="message"></td> -
extensions/net.sf.basedb.reggie/branches/ticket-489/src/net/sf/basedb/reggie/dao/HeGlass.java
r2123 r2125 12 12 import net.sf.basedb.core.ItemQuery; 13 13 import net.sf.basedb.core.Sample; 14 import net.sf.basedb.core.query.Annotations; 15 import net.sf.basedb.core.query.Expressions; 14 16 import net.sf.basedb.core.query.Hql; 17 import net.sf.basedb.core.query.Orders; 15 18 import net.sf.basedb.core.query.Restrictions; 16 19 import net.sf.basedb.reggie.JsonUtil; … … 90 93 query.join(Hql.innerJoin("bw", "bioPlate", "bp")); 91 94 query.restrict(Restrictions.eq(Hql.alias("bp"), Hql.entity(heGlass))); 95 query.order(Orders.asc(Hql.property("bw", "row"))); 96 query.order(Orders.asc(Hql.property("bw", "column"))); 92 97 93 98 for (Sample s : query.list(dc)) … … 110 115 jsonSamples.add(jsonSample); 111 116 } 117 } 118 119 public long countUncompletedSamples(DbControl dc) 120 { 121 BioPlate heGlass = getBioPlate(); 122 ItemQuery<Sample> query = Sample.getQuery(); 123 query.join(Hql.innerJoin("bioWell", "bw")); 124 query.join(Hql.innerJoin("bw", "bioPlate", "bp")); 125 query.restrict(Restrictions.eq(Hql.alias("bp"), Hql.entity(heGlass))); 126 query.join(Annotations.leftJoin(null, Annotationtype.SCORE_COMPLETE.load(dc), "sc")); 127 query.join(Annotations.leftJoin(null, Annotationtype.GOOD_STAIN.load(dc), "gs")); 128 query.restrict( 129 Restrictions.or( 130 Restrictions.eq(Hql.alias("sc"), Expressions.bool(false)), 131 Restrictions.and( 132 Restrictions.eq(Hql.alias("gs"), Expressions.bool(true)), 133 Restrictions.eq(Hql.alias("sc"), null) 134 ) 135 )); 112 136 137 query.order(Orders.asc(Hql.property("bw", "row"))); 138 query.order(Orders.asc(Hql.property("bw", "column"))); 139 return query.count(dc); 113 140 } 114 141 -
extensions/net.sf.basedb.reggie/branches/ticket-489/src/net/sf/basedb/reggie/servlet/HistologyServlet.java
r2124 r2125 427 427 for (HeGlass heGlass : plates) 428 428 { 429 heGlass.setAnnotation("numUncompleteSamples", heGlass.countUncompletedSamples(dc)); 429 430 jsonHeGlasses.add(heGlass.asJSONObject()); 430 431 } … … 769 770 { 770 771 dc.saveItem(stainEvent); 772 } 773 774 dc.commit(); 775 } 776 else if ("SaveHeScore".equals(cmd)) 777 { 778 JSONObject jsonReq = (JSONObject)new JSONParser().parse(req.getReader()); 779 JSONArray jsonSamples = (JSONArray)jsonReq.get("samples"); 780 JSONArray jsonHeGlass = (JSONArray)jsonReq.get("heGlass"); 781 dc = sc.newDbControl(); 782 783 // Update scores on samples 784 for (int sampleNo = 0; sampleNo < jsonSamples.size(); ++sampleNo) 785 { 786 JSONObject jsonSample = (JSONObject)jsonSamples.get(sampleNo); 787 Number sampleId = (Number)jsonSample.get("id"); 788 Boolean goodStain = (Boolean)jsonSample.get("GoodStain"); 789 Boolean scoreComplete = (Boolean)jsonSample.get("ScoreComplete"); 790 791 Sample he = Sample.getById(dc, sampleId.intValue()); 792 793 Annotationtype.GOOD_STAIN.setAnnotationValue(dc, he, goodStain); 794 Annotationtype.SCORE_INVASIVE_CANCER.setAnnotationValue(dc, he, jsonSample.get("ScoreInvasiveCancer")); 795 Annotationtype.SCORE_INSITU_CANCER.setAnnotationValue(dc, he, jsonSample.get("ScoreInsituCancer")); 796 Annotationtype.SCORE_LYMPHOCYTES.setAnnotationValue(dc, he, jsonSample.get("ScoreLymphocytes")); 797 Annotationtype.SCORE_NORMAL.setAnnotationValue(dc, he, jsonSample.get("ScoreNormal")); 798 Annotationtype.SCORE_STROMA.setAnnotationValue(dc, he, jsonSample.get("ScoreStroma")); 799 Annotationtype.SCORE_FAT.setAnnotationValue(dc, he, jsonSample.get("ScoreFat")); 800 Annotationtype.SCORE_COMPLETE.setAnnotationValue(dc, he, scoreComplete); 801 he.setDescription(Values.getStringOrNull((String)jsonSample.get("comments"))); 802 803 if (Boolean.TRUE.equals(scoreComplete)) 804 { 805 jsonMessages.add("Scoring complete for sample: " + he.getName() + (Boolean.TRUE.equals(goodStain) ? " (GoodStain)" : "")); 806 } 807 else if (Boolean.FALSE.equals(scoreComplete)) 808 { 809 jsonMessages.add("Score updated for sample: " + he.getName() + " (Not complete)"); 810 } 811 else 812 { 813 jsonMessages.add("Scores removed from sample: " + he.getName()); 814 } 815 } 816 817 // ScoreComplete on HE glass 818 for (int glassNo = 0; glassNo < jsonHeGlass.size(); ++glassNo) 819 { 820 JSONObject jsonGlass = (JSONObject)jsonHeGlass.get(glassNo); 821 Number glassId = (Number)jsonGlass.get("id"); 822 Boolean scoreComplete = (Boolean)jsonGlass.get("ScoreComplete"); 823 824 BioPlate heGlass = BioPlate.getById(dc, glassId.intValue()); 825 826 Annotationtype.SCORE_COMPLETE.setAnnotationValue(dc, heGlass, scoreComplete); 827 828 if (Boolean.TRUE.equals(scoreComplete)) 829 { 830 jsonMessages.add("Scoring complete for HE glass: " + heGlass.getName()); 831 } 832 else 833 { 834 jsonMessages.add("Score updated for HE glass: " + heGlass.getName() + " (Not complete)"); 835 836 } 771 837 } 772 838
Note: See TracChangeset
for help on using the changeset viewer.