source: trunk/www/biomaterials/wizards/move_biomaterial.jsp @ 5539

Last change on this file since 5539 was 5539, checked in by Nicklas Nordborg, 12 years ago

References #1564: Move biomaterial on plates

Implemented "Place by row" and "Place by column" predefined mappings. Changes to the interface to make it work better with two plates (the plates should be aligned).

File size: 21.0 KB
Line 
1<%-- $Id$
2  ------------------------------------------------------------------
3  Copyright (C) 2010 Nicklas Nordborg
4
5  This file is part of BASE - BioArray Software Environment.
6  Available at http://base.thep.lu.se/
7
8  BASE is free software; you can redistribute it and/or
9  modify it under the terms of the GNU General Public License
10  as published by the Free Software Foundation; either version 3
11  of the License, or (at your option) any later version.
12
13  BASE is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  GNU General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with BASE. If not, see <http://www.gnu.org/licenses/>.
20  ------------------------------------------------------------------
21
22  @author Nicklas
23--%>
24<%@page import="net.sf.basedb.core.BioPlateEventType"%>
25<%@ page pageEncoding="UTF-8" session="false"
26  import="net.sf.basedb.core.BioPlate"
27  import="net.sf.basedb.core.BioPlateEvent"
28  import="net.sf.basedb.core.MeasuredBioMaterial"
29  import="net.sf.basedb.core.Hardware"
30  import="net.sf.basedb.core.Protocol"
31  import="net.sf.basedb.core.DbControl"
32  import="net.sf.basedb.core.Item"
33  import="net.sf.basedb.core.ItemContext"
34  import="net.sf.basedb.core.ItemQuery"
35  import="net.sf.basedb.core.SessionControl"
36  import="net.sf.basedb.core.query.Restrictions"
37  import="net.sf.basedb.core.query.Hql"
38  import="net.sf.basedb.util.Values"
39  import="net.sf.basedb.util.formatter.Formatter"
40  import="net.sf.basedb.clients.web.formatter.FormatterFactory"
41  import="net.sf.basedb.clients.web.formatter.FormatterSettings"
42  import="net.sf.basedb.clients.web.Base"
43  import="net.sf.basedb.clients.web.util.HTML"
44  import="java.util.List"
45  import="java.util.Date"
46  import="java.util.Collections"
47%>
48<%@ taglib prefix="base" uri="/WEB-INF/base.tld" %>
49<%@ taglib prefix="tbl" uri="/WEB-INF/table.tld" %>
50<%
51final SessionControl sc = Base.getExistingSessionControl(pageContext, true);
52final String ID = sc.getId();
53final float scale = Base.getScale(sc);
54final int sourcePlateId = Values.getInt(request.getParameter("sourceplate_id"));
55final ItemContext cc = sc.getCurrentContext(Item.BIOPLATEEVENT, BioPlateEventType.MOVE);
56final DbControl dc = sc.newDbControl();
57try
58{
59  BioPlate sourcePlate = BioPlate.getById(dc, sourcePlateId);
60 
61  // Load recently used items
62  List<Hardware> recentHardware = (List<Hardware>)cc.getRecent(dc, Item.HARDWARE);
63  List<Protocol> recentProtocols = (List<Protocol>)cc.getRecent(dc, Item.PROTOCOL);
64 
65  Item itemType = Item.SAMPLE;
66 
67  final String clazz = "class=\"text\"";
68  final String requiredClazz = "class=\"text required\"";
69  final Formatter<Date> dateFormatter = FormatterFactory.getDateFormatter(sc);
70  final String dateFormat = FormatterSettings.getDateFormat(sc);
71  final String jsDateFormat = HTML.javaScriptEncode(dateFormat);
72  final String htmlDateFormat = HTML.encodeTags(dateFormat);
73  %>
74  <base:page type="popup" title="Move biomaterial">
75  <base:head scripts="ajax.js,plate.js,js-draw.js" styles="plate.css,toolbar.css">
76    <script language="javascript">
77 
78    // For drawing
79    var graphics;
80    var pen;
81    var selectedPen;
82   
83    // Source and destination plates
84    var sourcePlate;
85    var destPlate;
86   
87    // We can select one well on each plate
88    var selectedSourceWell;
89    var selectedDestWell;
90
91    function init()
92    {
93      // Initialize graphics
94      graphics = new jsGraphics(document.getElementById('canvas'));
95      pen = new jsPen();
96      selectedPen = new jsPen(new jsColor('445577'), 2);
97      setBioPlateCallback(<%=sourcePlateId%>, '<%=HTML.javaScriptEncode(sourcePlate.getName())%>', true);
98    }
99
100    /**
101      Select the source well that is clicked on. The currently selected well
102      is de-selected. If the clicked well is the same as the currently
103      selected well no new item is selected. If a destination well is already
104      selected a link is made between the source and destination wells.
105    */
106    function sourceWellOnClick(row, column)
107    {
108      var well = sourcePlate.getWell(row, column);
109      if (!well) return;
110
111      // De-select the currently selected source well
112      if (selectedSourceWell)
113      {
114        selectedSourceWell.setSelected(false);
115        if (well == selectedSourceWell)
116        {
117          // Re-draw link with regular pen (eg. same as onmouseover)
118          selectedSourceWell.drawLink(graphics, pen, true);
119          selectedSourceWell = null;
120          return;
121        }
122        else
123        {
124          selectedSourceWell.hideLink(graphics);
125        }
126      }
127     
128      // Select the new source well and draw link to destination well
129      selectedSourceWell = well;
130      selectedSourceWell.setSelected(true);
131      selectedSourceWell.drawLink(graphics, selectedPen, true);
132
133      // Map the source and destination wells
134      if (selectedSourceWell && selectedDestWell)
135      {
136        mapSelectedWells();
137      }
138    }
139   
140    /**
141      Draw a link between the mapped source and destination wells.
142    */
143    function sourceWellOnMouseOver(row, column)
144    {
145      var well = sourcePlate.getWell(row, column);
146      if (!well) return;
147      well.drawLink(graphics, pen, false);
148    }
149   
150    /**
151      Hide the link between the mapped source and destination
152      wells, unless one of them is selected.
153    */
154    function sourceWellOnMouseOut(row, column)
155    {
156      var well = sourcePlate.getWell(row, column);
157      if (!well) return;
158      if (!well.selected && !(well.mappedWell && well.mappedWell.selected))
159      {
160        well.hideLink(graphics);
161      }
162    }
163   
164    function destWellOnClick(row, column)
165    {
166      var well = destPlate.getWell(row, column);
167
168      // De-select the currently selected well
169      if (selectedDestWell)
170      {
171        selectedDestWell.setSelected(false);
172        if (well == selectedDestWell)
173        {
174          // Re-draw link with regular pen (eg. same as onmouseover)
175          selectedDestWell.drawLink(graphics, pen, true);
176          selectedDestWell = null;
177          return;
178        }
179        else
180        {
181          selectedDestWell.hideLink(graphics);
182        }
183      }
184     
185      selectedDestWell = well;
186      selectedDestWell.setSelected(true);
187      selectedDestWell.drawLink(graphics, selectedPen, true);
188
189      if (selectedSourceWell && selectedDestWell)
190      {
191        mapSelectedWells();
192      }
193    }
194   
195    function destWellOnMouseOver(row, column)
196    {
197      var well = destPlate.getWell(row, column);
198      if (!well) return;
199      well.drawLink(graphics, pen, false);
200    }
201   
202    function destWellOnMouseOut(row, column)
203    {
204      var well = destPlate.getWell(row, column);
205      if (!well) return;
206      if (!well.selected && !(well.mappedWell && well.mappedWell.selected))
207      {
208        well.hideLink(graphics);
209      }
210    }
211
212    /**
213      Map the selected source and destination wells. Hide and
214      redraw links as needed.
215    */
216    function mapSelectedWells()
217    {
218      // Hide any links that are currently displayed
219      selectedSourceWell.hideLink(graphics);
220      selectedDestWell.hideLink(graphics);
221     
222      // Map to the new well and draw link
223      if (selectedSourceWell.mappedWell != selectedDestWell)
224      {
225        selectedSourceWell.mapToWell(selectedDestWell);
226        selectedSourceWell.drawLink(graphics, pen, true);
227      }
228      else
229      {
230        selectedSourceWell.unmapWell();
231      }
232     
233      // De-select everything
234      selectedSourceWell.setSelected(false);
235      selectedDestWell.setSelected(false);
236      selectedSourceWell = null;
237      selectedDestWell = null;
238    }
239
240   
241    /**
242      Remove all mappings that have been made so far.
243    */
244    function clearMapping()
245    {
246      if (!destPlate)
247      {
248        alert('No destination plate has been selected');
249        return;
250      }
251      if (!confirm('This will remove all placed biomaterial. Continue?')) return;
252      sourcePlate.unmapAll();
253    }
254   
255    /**
256      Automatically move remaining biomaterial to the destination plate filling rows first.
257      Biomaterial and wells that have already been mapped are skipped.
258    */
259    function placeByRow()
260    {
261      if (!destPlate)
262      {
263        alert('No destination plate has been selected');
264        return;
265      }
266      var srcRow = 0;
267      var srcCol = 0;
268      for (var destRow = 0; destRow < destPlate.rows; destRow++)
269      {
270        for (var destCol = 0; destCol < destPlate.columns; destCol++)
271        {
272          var destWell = destPlate.getWell(destRow, destCol);
273          if (!destWell.mappedWell && !destWell.locked)
274          {
275            var mapped = false;
276            var srcWell = sourcePlate.getWell(srcRow, srcCol);
277            while (srcWell && !mapped)
278            {
279              if (srcWell.id && !srcWell.locked && !srcWell.mappedWell)
280              {
281                srcWell.mapToWell(destWell);
282                mapped = true;
283              }
284              else
285              {
286                srcCol++;
287                if (srcCol >= sourcePlate.columns)
288                {
289                  srcCol = 0;
290                  srcRow++;
291                }
292                srcWell = sourcePlate.getWell(srcRow, srcCol);
293              }
294            }
295          }
296        }
297      }
298    }
299 
300    /**
301      Automatically move remaining biomaterial to the destination plate filling columns first.
302      Biomaterial and wells that have already been mapped are skipped.
303    */
304    function placeByColumn()
305    {
306      if (!destPlate)
307      {
308        alert('No destination plate has been selected');
309        return;
310      }
311      var srcRow = 0;
312      var srcCol = 0;
313      for (var destCol = 0; destCol < destPlate.columns; destCol++)
314      {
315        for (var destRow = 0; destRow < destPlate.rows; destRow++)
316        {
317          var destWell = destPlate.getWell(destRow, destCol);
318          if (!destWell.mappedWell && !destWell.locked)
319          {
320            var mapped = false;
321            var srcWell = sourcePlate.getWell(srcRow, srcCol);
322            while (srcWell && !mapped)
323            {
324              if (srcWell.id && !srcWell.locked && !srcWell.mappedWell)
325              {
326                srcWell.mapToWell(destWell);
327                mapped = true;
328              }
329              else
330              {
331                srcRow++;
332                if (srcRow >= sourcePlate.rows)
333                {
334                  srcCol++;
335                  srcRow = 0;
336                }
337                srcWell = sourcePlate.getWell(srcRow, srcCol);
338              }
339            }
340          }
341        }
342      }
343    }
344
345   
346    function selectBioPlateOnClick()
347    {
348      if (destPlate && !confirm('This will reset all moved biomaterial. Continue?')) return;
349     
350      var url = '../bioplates/index.jsp?ID=<%=ID%>&cmd=UpdateContext&mode=selectone&callback=setBioPlateCallback';
351      url += '&resetTemporary=1&tmpfilter:INT:bioPlateType.bioMaterialType=|<%=itemType.getValue()%>'
352      url += '&exclude='+sourcePlate.id;
353      Main.openPopup(url, 'SelectBioPlate', 1000, 700);
354    }
355
356    function setBioPlateCallback(bioPlateId, name, isSourcePlate)
357    {
358      if (!isSourcePlate && bioPlateId == sourcePlate.id)
359      {
360        alert('It is not possible to use the same plate as both source and destination plate.');
361        return;
362      }
363     
364      var request = Ajax.getXmlHttpRequest();
365      var url = '../bioplates/ajax.jsp?ID=<%=ID%>&cmd=GetFullPlateInfo&item_id=' + bioPlateId;
366      request.open("GET", url, false);
367      // NOTE! 'false' causes code to wait for the response. aka. 'Synchronous AJAX' or SJAX.
368      request.send(null);
369      var response = Ajax.parseResponse(request.responseText);
370      if (response.isError())
371      {
372        alert(response.getErrorMessage());
373        return false;
374      }
375
376      // Get plate and well information from the AJAX response
377      var records = response.getElements();
378      var plateInfo = records[0];
379      var rows = parseInt(plateInfo['rows']);
380      var columns = parseInt(plateInfo['columns']);
381     
382      var bigPlate = rows > 12 || columns > 18;
383      var plateClass = bigPlate ? 'plate bigplate' : 'plate';
384
385      // Remove existing mapping
386      if (sourcePlate) sourcePlate.unmapAll();
387
388      // Create plate and wells
389      var prefix = isSourcePlate ? 'source' : 'dest';
390      var plate = new Plate(prefix, plateInfo['id'], plateInfo['name'], rows, columns, isSourcePlate);
391      for (var i = 1; i < records.length; i++)
392      {
393        var wellInfo = records[i];
394        var row = parseInt(wellInfo['well.row']);
395        var col = parseInt(wellInfo['well.column']);
396        var well = plate.getWell(row, col);
397        well.id = wellInfo['bm.id'];
398        well.name = wellInfo['bm.name'];
399        well.locked = isSourcePlate ? wellInfo['well.can-clear'] == '0' : wellInfo['well.can-add'] == '0' || well.id;
400      }
401     
402      // Create html table representing the bioplate
403      var html = '<table class="'+plateClass+'" cellspacing="0" cellpadding="0" onmouseout="event.cancelBubble=true">';
404      html += '<tr><td></td>';
405      for (var c = 0; c < plate.columns; c++)
406      {
407        html += '<td class="columnheader">' + (c+1) + '</td>';
408      }
409      html += '</tr>';
410      for (var r = 0; r < plate.rows; r++)
411      {
412        html += '<tr><td class="rowheader">' + Plates.toAlphaCoordinate[r] + '</td>';
413        for (var c = 0; c < plate.columns; c++)
414        {
415          var well = plate.getWell(r, c);
416          var cls = 'well';
417          var onclick = '';
418          var onmouseover = '';
419          var onmouseout = '';
420          var title = well.name ? Main.encodeTags(well.name) : '';
421          if (isSourcePlate)
422          {
423            // We can only move a biomaterial if the well is not empty or locked
424            if (well.id)
425            {
426              if (well.locked)
427              {
428                cls += ' filled locked';
429              }
430              else
431              {
432                cls += ' filled editable';
433                onclick = ' onclick="sourceWellOnClick('+r+','+c+')"';
434                onmouseover = ' onmouseover=sourceWellOnMouseOver('+r+','+c+')';
435                onmouseout = ' onmouseout=sourceWellOnMouseOut('+r+','+c+')';
436              }
437            }
438            else
439            {
440              cls += ' empty';
441            }
442          }
443          else
444          {
445            // We only add biomaterial to destination well if it is not locked
446            if (well.locked)
447            {
448              cls += ' unmappable';
449            }
450            else
451            {
452              cls += ' empty editable';
453              onclick = ' onclick="destWellOnClick('+r+','+c+')"';
454              onmouseover = ' onmouseover=destWellOnMouseOver('+r+','+c+')';
455              onmouseout = ' onmouseout=destWellOnMouseOut('+r+','+c+')';
456            }
457          }
458          html += '<td id="'+prefix+'.'+r+'.'+c+'" class="' + cls + '"' + onclick + onmouseover + onmouseout+' title="'+title+'"></td>';
459        }
460        html += '</tr>';
461      }
462      html += '</table>';
463      if (isSourcePlate)
464      {
465        document.getElementById('plate.src').innerHTML = html;
466        document.getElementById('plate.src.name').innerHTML = Main.encodeTags(name);
467        sourcePlate = plate;
468      }
469      else
470      {
471        document.getElementById('plate.dest').innerHTML = html;
472        document.getElementById('plate.dest.name').innerHTML = Main.encodeTags(name);
473        destPlate = plate;
474        Main.show('toolbar.mappings');
475      }
476    }
477   
478    /**
479      Save the event.
480    */
481    function doMoveBioMaterial()
482    {
483      if (!destPlate)
484      {
485        alert('No destination plate has been selected');
486        return;
487      }
488      var frm = document.forms['main'];
489      if (Main.trimString(frm.name.value) == '')
490      {
491        alert("You must enter a name for the event.");
492        frm.name.focus();
493        return;
494      }
495     
496      frm.destplate_id.value = destPlate.id;
497      frm.rows.value = destPlate.rows;
498      frm.columns.value = destPlate.columns;
499     
500      var numMapped = 0;
501      for (var row = 0; row < destPlate.rows; row++)
502      {
503        for (var column = 0; column < destPlate.columns; column++)
504        {
505          var well = destPlate.getWell(row, column);
506         
507          if (well.mappedWell)
508          {
509            Forms.createHidden(frm, 'well.' + row + '.' + column, well.mappedWell.id);
510            numMapped++;
511          }
512        }
513      }
514     
515      if (numMapped == 0)
516      {
517        alert('No wells have been mapped.');
518        return;
519      }
520     
521      frm.submit();
522    }
523   
524
525    function selectHardwareOnClick()
526    {
527      var frm = document.forms['main'];
528      var url = '../../admin/hardware/index.jsp?ID=<%=ID%>&cmd=UpdateContext&mode=selectone&callback=setHardwareCallback';
529      if (frm.hardware_id.length > 1)
530      {
531        var id = Math.abs(parseInt(frm.hardware_id[1].value));       
532        url += '&item_id='+id;
533      }
534      url += '&resetTemporary=1';
535      Main.openPopup(url, 'SelectHardware', 1000, 700);
536    }
537    function setHardwareCallback(id, name)
538    {
539      var frm = document.forms['main'];
540      var list = frm.hardware_id;
541      if (list.length < 2 || list[1].value == '0') // >
542      {
543        Forms.addListOption(list, 1, new Option());
544      }
545      list[1].value = id;
546      list[1].text = name;
547      list.selectedIndex = 1;
548    }
549 
550    function selectProtocolOnClick()
551    {
552      var frm = document.forms['main'];
553      var url = '../../admin/protocols/index.jsp?ID=<%=ID%>&cmd=UpdateContext&mode=selectone&callback=setProtocolCallback';
554      if (frm.protocol_id.length > 1)
555      {
556        var id = Math.abs(parseInt(frm.protocol_id[1].value));       
557        url += '&item_id='+id;
558      }
559      url += '&resetTemporary=1';
560      Main.openPopup(url, 'SelectProtocol', 1000, 700);
561    }
562    function setProtocolCallback(id, name)
563    {
564      var frm = document.forms['main'];
565      var list = frm.protocol_id;
566      if (list.length < 2 || list[1].value == '0') // >
567      {
568        Forms.addListOption(list, 1, new Option());
569      }
570      list[1].value = id;
571      list[1].text = name;
572      list.selectedIndex = 1;
573    }
574    </script>
575  </base:head>
576  <base:body onload="init()">
577    <div id="canvas"></div>
578    <h3>Move biomaterial to plate</h3>
579    <div class="boxedbottom" style="height: <%=(int)(scale*500)%>px; padding: 0px;">
580      <form name="main" action="index.jsp" method="post">
581      <input type="hidden" name="ID" value="<%=ID%>">
582      <input type="hidden" name="cmd" value="MoveBioMaterial">
583      <input type="hidden" name="sourceplate_id" value="<%=sourcePlateId%>">
584      <input type="hidden" name="destplate_id" value="">
585      <input type="hidden" name="rows" value="">
586      <input type="hidden" name="columns" value=""> 
587     
588      <table cellspacing="0" border="0" width="100%" style="background: #e0e0e0; padding-bottom: 6px;">
589      <tr valign="top">
590      <td>
591        <table class="form" cellspacing="0">
592        <tr>
593          <td class="prompt" style="width: 90px;">Event name</td>
594          <td><input <%=requiredClazz%> type="text" name="name" 
595            value="Move biomaterial" 
596            size="40" maxlength="<%=BioPlateEvent.MAX_NAME_LENGTH%>"></td>
597        </tr>
598        <tr>
599          <td class="prompt">Event date</td>
600          <td>
601            <table border="0" cellspacing="0" cellpadding="0">
602            <tr>
603            <td>
604              <input <%=clazz%> type="text" name="event_date" 
605                value="<%=HTML.encodeTags(dateFormatter.format(new Date()))%>" 
606                size="20" maxlength="20" title="Enter date in format: <%=htmlDateFormat%>">
607              &nbsp;
608            </td>
609            <td>
610            <base:button 
611              onclick="<%="Dates.selectDate('Event date', 'main', 'event_date', null, '"+jsDateFormat+"')"%>"
612              image="calendar.png"
613              title="Calendar&hellip;" 
614              tooltip="Select a date from a calendar" 
615            />
616            </td>
617            </tr>
618            </table>
619          </td>
620        </tr>
621        <tr>
622          <td class="prompt">Protocol</td>
623          <td>
624            <base:select 
625              id="protocol_id"
626              clazz="selectionlist"
627              required="false"
628              current="<%=null%>"
629              recent="<%=recentProtocols%>"
630              onselect="selectProtocolOnClick()"
631            />
632          </td>
633        </tr>
634        <tr>
635          <td class="prompt">Hardware</td>
636          <td>
637            <base:select 
638              id="hardware_id"
639              clazz="selectionlist"
640              required="false"
641              current="<%=null%>"
642              recent="<%=recentHardware%>"
643              onselect="selectHardwareOnClick()"
644            />
645          </td>
646        </tr>
647        </table>
648      </td>
649      <td>
650        <b>Description</b><br>
651        <textarea <%=clazz%> rows="4" cols="40" name="description" wrap="virtual"
652          ></textarea>
653        <a href="javascript:Main.zoom('Description', 'main', 'description')"
654          title="Edit in larger window"><base:icon image="zoom.gif" /></a>
655      </td>
656    </tr>
657    </table>
658
659    <tbl:toolbar id="toolbar.mappings" style="border-left: 0px; border-right: 0px;">
660      <tbl:button title="Select plate&hellip;" 
661          onclick="javascript:selectBioPlateOnClick()" 
662          image="move_to_plate.png" 
663          tooltip="Select the destination plate"
664        />
665        <tbl:button title="Clear" 
666          onclick="clearMapping()" 
667          image="cancel.gif" 
668          tooltip="Clear all mapped wells"
669        />
670        <tbl:button title="Place by row" 
671          onclick="placeByRow()" 
672          image="place_by_row.png" 
673          tooltip="Place remaining items; start with rows"
674        />
675        <tbl:button title="Place by column" 
676          onclick="placeByColumn()" 
677          image="place_by_column.png" 
678          tooltip="Place remaining items; start with columns"
679        />
680      </tbl:toolbar>
681 
682      <table cellspacing="0" cellpadding="0" border="0" width="100%" style="padding: 4px;">
683      <tr valign="top">
684        <td style="width: 50%;">
685          <div style="max-height: <%=(int)(scale*350)%>px; overflow: auto;">
686            <b>Source plate:</b> <span id="plate.src.name"></span>
687            <div id="plate.src"></div>
688          </div>
689        </td>
690        <td style="width: 50%;">
691          <div style="max-height: <%=(int)(scale*350)%>px; overflow: auto;">
692            <b>Destination plate:</b> <span id="plate.dest.name"><i>not selected</i></span>
693            <div id="plate.dest"></div>
694          </div>
695        </td>
696      </tr>
697      </table>
698    </div>
699    </form>
700
701    <table align="center">
702    <tr>
703      <td width="50%"><base:button onclick="doMoveBioMaterial()" title="Save" /></td>
704      <td width="50%"><base:button onclick="window.close()" title="Cancel" /></td>
705    </tr>
706    </table>
707   
708  </base:body>
709  </base:page>
710  <%
711}
712finally
713{
714  if (dc != null) dc.close();
715}
716%>
Note: See TracBrowser for help on using the repository browser.