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

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

References #1536, #1559 and #1564. Cleaned up the "mess" in the stylesheet and icons used to indicate different well status on bioplates.

File size: 19.4 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    function selectBioPlateOnClick()
242    {
243      if (destPlate && !confirm('This will reset all moved biomaterial. Continue?')) return;
244     
245      var url = '../bioplates/index.jsp?ID=<%=ID%>&cmd=UpdateContext&mode=selectone&callback=setBioPlateCallback';
246      url += '&resetTemporary=1&tmpfilter:INT:bioPlateType.bioMaterialType=|<%=itemType.getValue()%>'
247      url += '&exclude='+sourcePlate.id;
248      Main.openPopup(url, 'SelectBioPlate', 1000, 700);
249    }
250
251    function setBioPlateCallback(bioPlateId, name, isSourcePlate)
252    {
253      if (!isSourcePlate && bioPlateId == sourcePlate.id)
254      {
255        alert('It is not possible to use the same plate as both source and destination plate.');
256        return;
257      }
258     
259      var request = Ajax.getXmlHttpRequest();
260      var url = '../bioplates/ajax.jsp?ID=<%=ID%>&cmd=GetFullPlateInfo&item_id=' + bioPlateId;
261      request.open("GET", url, false);
262      // NOTE! 'false' causes code to wait for the response. aka. 'Synchronous AJAX' or SJAX.
263      request.send(null);
264      var response = Ajax.parseResponse(request.responseText);
265      if (response.isError())
266      {
267        alert(response.getErrorMessage());
268        return false;
269      }
270
271      // Get plate and well information from the AJAX response
272      var records = response.getElements();
273      var plateInfo = records[0];
274      var rows = parseInt(plateInfo['rows']);
275      var columns = parseInt(plateInfo['columns']);
276     
277      var bigPlate = rows > 12 || columns > 18;
278      var plateClass = bigPlate ? 'plate bigplate' : 'plate';
279
280      // Remove existing mapping
281      if (sourcePlate) sourcePlate.unmapAll();
282
283      // Create plate and wells
284      var prefix = isSourcePlate ? 'source' : 'dest';
285      var plate = new Plate(prefix, plateInfo['id'], plateInfo['name'], rows, columns, isSourcePlate);
286      for (var i = 1; i < records.length; i++)
287      {
288        var wellInfo = records[i];
289        var row = parseInt(wellInfo['well.row']);
290        var col = parseInt(wellInfo['well.column']);
291        var well = plate.getWell(row, col);
292        well.id = wellInfo['bm.id'];
293        well.name = wellInfo['bm.name'];
294        well.locked = isSourcePlate ? wellInfo['well.can-clear'] == '0' : wellInfo['well.can-add'] == '0';
295      }
296     
297      // Create html table representing the bioplate
298      var html = '<table class="'+plateClass+'" cellspacing="0" cellpadding="0" onmouseout="event.cancelBubble=true">';
299      html += '<tr><td></td>';
300      for (var c = 0; c < plate.columns; c++)
301      {
302        html += '<td class="columnheader">' + (c+1) + '</td>';
303      }
304      html += '</tr>';
305      for (var r = 0; r < plate.rows; r++)
306      {
307        html += '<tr><td class="rowheader">' + Plates.toAlphaCoordinate[r] + '</td>';
308        for (var c = 0; c < plate.columns; c++)
309        {
310          var well = plate.getWell(r, c);
311          var cls = 'well';
312          var onclick = '';
313          var onmouseover = '';
314          var onmouseout = '';
315          var title = well.name ? Main.encodeTags(well.name) : '';
316          if (isSourcePlate)
317          {
318            // We can only move a biomaterial if the well is not empty or locked
319            if (well.id)
320            {
321              if (well.locked)
322              {
323                cls += ' filled locked';
324              }
325              else
326              {
327                cls += ' filled editable';
328                onclick = ' onclick="sourceWellOnClick('+r+','+c+')"';
329                onmouseover = ' onmouseover=sourceWellOnMouseOver('+r+','+c+')';
330                onmouseout = ' onmouseout=sourceWellOnMouseOut('+r+','+c+')';
331              }
332            }
333            else
334            {
335              cls += ' empty';
336            }
337          }
338          else
339          {
340            // We only add biomaterial to destination well if it is empty and not locked
341            if (well.id || well.locked)
342            {
343              cls += ' unmappable';
344            }
345            else
346            {
347              cls += ' empty editable';
348              onclick = ' onclick="destWellOnClick('+r+','+c+')"';
349              onmouseover = ' onmouseover=destWellOnMouseOver('+r+','+c+')';
350              onmouseout = ' onmouseout=destWellOnMouseOut('+r+','+c+')';
351            }
352          }
353          /*
354          if (well.id) 
355          {
356            if (isSourcePlate && !well.locked)
357            {
358              cls += ' filled editable';
359              onclick = ' onclick="sourceWellOnClick('+r+','+c+')""';
360              onmouseover = ' onmouseover=sourceWellOnMouseOver('+r+','+c+')';
361              onmouseout = ' onmouseout=sourceWellOnMouseOut('+r+','+c+')';
362            }
363            else
364            {
365              if (isSourcePlate)
366              {
367                cls+= ' filled locked';
368              }
369              else
370              {
371                cls += ' unmappable';
372              }
373            }
374            if (well.name) title = Main.encodeTags(well.name);
375          }
376          else
377          {
378            if (isSourcePlate)
379            {
380              cls += ' empty';
381            }
382            else
383            {
384              cls += ' empty editable';
385              onclick = ' onclick="destWellOnClick('+r+','+c+')""';
386              onmouseover = ' onmouseover=destWellOnMouseOver('+r+','+c+')';
387              onmouseout = ' onmouseout=destWellOnMouseOut('+r+','+c+')';
388            }
389          }
390          */
391          html += '<td id="'+prefix+'.'+r+'.'+c+'" class="' + cls + '"' + onclick + onmouseover + onmouseout+' title="'+title+'"></td>';
392        }
393        html += '</tr>';
394      }
395      html += '</table>';
396      if (isSourcePlate)
397      {
398        document.getElementById('plate.src').innerHTML = html;
399        document.getElementById('plate.src.name').innerHTML = Main.encodeTags(name);
400        sourcePlate = plate;
401      }
402      else
403      {
404        document.getElementById('plate.dest').innerHTML = html;
405        document.getElementById('plate.dest.name').innerHTML = Main.encodeTags(name);
406        destPlate = plate;
407      }
408    }
409   
410    /**
411      Save the event.
412    */
413    function doMoveBioMaterial()
414    {
415      if (!destPlate)
416      {
417        alert('No destination plate has been selected');
418        return;
419      }
420      var frm = document.forms['main'];
421      if (Main.trimString(frm.name.value) == '')
422      {
423        alert("You must enter a name for the event.");
424        frm.name.focus();
425        return;
426      }
427     
428      frm.destplate_id.value = destPlate.id;
429      frm.rows.value = destPlate.rows;
430      frm.columns.value = destPlate.columns;
431     
432      var numMapped = 0;
433      for (var row = 0; row < destPlate.rows; row++)
434      {
435        for (var column = 0; column < destPlate.columns; column++)
436        {
437          var well = destPlate.getWell(row, column);
438         
439          if (well.mappedWell)
440          {
441            Forms.createHidden(frm, 'well.' + row + '.' + column, well.mappedWell.id);
442            numMapped++;
443          }
444        }
445      }
446     
447      if (numMapped == 0)
448      {
449        alert('No wells have been mapped.');
450        return;
451      }
452     
453      frm.submit();
454    }
455   
456    function showMoreEventProperties()
457    {
458      Main.show('moreeventproperties');
459      Main.hide('more');
460    }
461   
462    function hideMoreEventProperties()
463    {
464      Main.hide('moreeventproperties');
465      Main.show('more');
466    }
467
468    function selectHardwareOnClick()
469    {
470      var frm = document.forms['main'];
471      var url = '../../admin/hardware/index.jsp?ID=<%=ID%>&cmd=UpdateContext&mode=selectone&callback=setHardwareCallback';
472      if (frm.hardware_id.length > 1)
473      {
474        var id = Math.abs(parseInt(frm.hardware_id[1].value));       
475        url += '&item_id='+id;
476      }
477      url += '&resetTemporary=1';
478      Main.openPopup(url, 'SelectHardware', 1000, 700);
479    }
480    function setHardwareCallback(id, name)
481    {
482      var frm = document.forms['main'];
483      var list = frm.hardware_id;
484      if (list.length < 2 || list[1].value == '0') // >
485      {
486        Forms.addListOption(list, 1, new Option());
487      }
488      list[1].value = id;
489      list[1].text = name;
490      list.selectedIndex = 1;
491    }
492 
493    function selectProtocolOnClick()
494    {
495      var frm = document.forms['main'];
496      var url = '../../admin/protocols/index.jsp?ID=<%=ID%>&cmd=UpdateContext&mode=selectone&callback=setProtocolCallback';
497      if (frm.protocol_id.length > 1)
498      {
499        var id = Math.abs(parseInt(frm.protocol_id[1].value));       
500        url += '&item_id='+id;
501      }
502      url += '&resetTemporary=1';
503      Main.openPopup(url, 'SelectProtocol', 1000, 700);
504    }
505    function setProtocolCallback(id, name)
506    {
507      var frm = document.forms['main'];
508      var list = frm.protocol_id;
509      if (list.length < 2 || list[1].value == '0') // >
510      {
511        Forms.addListOption(list, 1, new Option());
512      }
513      list[1].value = id;
514      list[1].text = name;
515      list.selectedIndex = 1;
516    }
517    </script>
518  </base:head>
519  <base:body onload="init()">
520    <div id="canvas"></div>
521    <h3>Move biomaterial to plate</h3>
522    <div class="boxedbottom" style="height: <%=(int)(scale*480)%>px; overflow: auto:">
523      <form name="main" action="index.jsp" method="post">
524      <input type="hidden" name="ID" value="<%=ID%>">
525      <input type="hidden" name="cmd" value="MoveBioMaterial">
526      <input type="hidden" name="sourceplate_id" value="<%=sourcePlateId%>">
527      <input type="hidden" name="destplate_id" value="">
528      <input type="hidden" name="rows" value="">
529      <input type="hidden" name="columns" value=""> 
530
531      <table cellspacing="0" border="0" width="100%">
532      <tr valign="top">
533        <td>
534          <table class="form" cellspacing=0>
535          <tr>
536            <td class="prompt" style="width: 90px;">Event name</td>
537            <td><input <%=requiredClazz%> type="text" name="name" 
538              value="Move biomaterial" 
539              size="40" maxlength="<%=BioPlateEvent.MAX_NAME_LENGTH%>"></td>
540          </tr>
541          <tr>
542            <td class="prompt">Event date</td>
543            <td>
544              <table border="0" cellspacing="0" cellpadding="0">
545              <tr>
546              <td>
547                <input <%=clazz%> type="text" name="event_date" 
548                  value="<%=HTML.encodeTags(dateFormatter.format(new Date()))%>" 
549                  size="20" maxlength="20" title="Enter date in format: <%=htmlDateFormat%>">
550                &nbsp;
551              </td>
552              <td>
553              <base:button 
554                onclick="<%="Dates.selectDate('Event date', 'main', 'event_date', null, '"+jsDateFormat+"')"%>"
555                image="calendar.png"
556                title="Calendar&hellip;" 
557                tooltip="Select a date from a calendar" 
558              />
559              </td>
560              </tr>
561              </table>
562            </td>
563          </tr>
564          <tr>
565            <td class="prompt">Protocol</td>
566            <td>
567              <base:select 
568                id="protocol_id"
569                clazz="selectionlist"
570                required="false"
571                current="<%=null%>"
572                recent="<%=recentProtocols%>"
573                onselect="selectProtocolOnClick()"
574              />
575            </td>
576          </tr>
577          <tr>
578            <td class="prompt">Hardware</td>
579            <td>
580              <base:select 
581                id="hardware_id"
582                clazz="selectionlist"
583                required="false"
584                current="<%=null%>"
585                recent="<%=recentHardware%>"
586                onselect="selectHardwareOnClick()"
587              />
588            </td>
589          </tr>
590          </table>
591        </td>
592        <td>
593          <b>Description</b><br>
594          <textarea <%=clazz%> rows="4" cols="40" name="description" wrap="virtual"
595            ></textarea>
596          <a href="javascript:Main.zoom('Description', 'main', 'description')"
597            title="Edit in larger window"><base:icon image="zoom.gif" /></a>
598        </td>
599      </tr>
600     
601      <tr>
602        <td><b>Source plate</b> (<span id="plate.src.name"></span>)</td>
603        <td><b>Destination plate</b> (<span id="plate.dest.name">no plate selected</span>) <a href="javascript:selectBioPlateOnClick()">Select plate...</a></td>
604      </tr>
605      <tr valign="top">
606        <td style="width: 50%;">
607          <div id="plate.src"></div>
608        </td>
609        <td style="width: 50%;">
610          <div style="height: <%=(int)(scale*400)%>px; overflow: auto;">
611         
612          <tbl:toolbar id="toolbar.mappings" style="display: none; margin-top: 6px;">
613            <tbl:button title="Clear" 
614              onclick="clearMapping()" 
615              image="cancel.gif" 
616              tooltip="Clear all mapped wells"
617            />
618            <tbl:button title="Place by row" 
619              onclick="placeByRow()" 
620              image="place_by_row.png" 
621              tooltip="Place remaining items; start with rows"
622            />
623            <tbl:button title="Place by column" 
624              onclick="placeByColumn()" 
625              image="place_by_column.png" 
626              tooltip="Place remaining items; start with columns"
627            />
628          </tbl:toolbar>
629          <div id="plate.dest"></div>
630          </div>
631        </td>
632      </tr>
633      </table>
634
635      </form>
636    </div>
637    <table align="center">
638    <tr>
639      <td width="50%"><base:button onclick="doMoveBioMaterial()" title="Save" /></td>
640      <td width="50%"><base:button onclick="window.close()" title="Cancel" /></td>
641    </tr>
642    </table>
643   
644  </base:body>
645  </base:page>
646  <%
647}
648finally
649{
650  if (dc != null) dc.close();
651}
652%>
Note: See TracBrowser for help on using the repository browser.