Merge pull request #5205 from solgenomics/topic/generic_trial_upload
[sgn.git] / js / source / legacy / tools / LabelDesigner.js
blob426815523ed9a838e01b623f2a87ed96626851d9
2 var page_formats = {};
3 page_formats["Select a page format"] = {};
4 page_formats["US Letter PDF"] = {
5     page_width: 611,
6     page_height: 790.7,
7     label_sizes: {
8             'Select a label format' : {},
9             '1" x 2 5/8"': {
10                 label_width: 189,
11                 label_height: 72,
12                 left_margin: 13.68,
13                 top_margin: 36.7,
14                 horizontal_gap: 10,
15                 vertical_gap: 0,
16                 number_of_columns: 3,
17                 number_of_rows: 10
18             },
19             '1" x 4"': {
20                 label_width: 288,
21                 label_height: 72,
22                 left_margin: 13.68,
23                 top_margin: 36.7,
24                 horizontal_gap: 10,
25                 vertical_gap: 0,
26                 number_of_columns: 2,
27                 number_of_rows: 10
28             },
29             '1 1/3" x 4"': {
30                 label_width: 288,
31                 label_height: 96,
32                 left_margin: 13.68,
33                 top_margin: 36.7,
34                 horizontal_gap: 10,
35                 vertical_gap: 0,
36                 number_of_columns: 2,
37                 number_of_rows: 7
38             },
39             '2" x 2 5/8"': {
40                 label_width: 189,
41                 label_height: 144,
42                 left_margin: 13.68,
43                 top_margin: 36.7,
44                 horizontal_gap: 10,
45                 vertical_gap: 0,
46                 number_of_columns: 3,
47                 number_of_rows: 5
48             },
49             'Custom': {
50                 label_width: 0,
51                 label_height: 0,
52                 left_margin: 13.68,
53                 top_margin: 36.7,
54                 horizontal_gap:0,
55                 vertical_gap:0,
56                 number_of_columns:1,
57                 number_of_rows:1
58             },
59             'CASS [1" x 5 1/4"]': {
60                 label_width: 378,
61                 label_height: 72,
62                 left_margin: 224,
63                 top_margin: 30,
64                 horizontal_gap: 10,
65                 vertical_gap: 1.5,
66                 number_of_columns: 1,
67                 number_of_rows: 10
68             },
69             'MUSA [1" x 5 1/4"]': {
70                 label_width: 378,
71                 label_height: 72,
72                 left_margin: 224,
73                 top_margin: 30,
74                 horizontal_gap: 10,
75                 vertical_gap: 1.5,
76                 number_of_columns: 1,
77                 number_of_rows: 10
78             },
79             'IITA-3 | IITA-2 [2/5" x 5 1/4"]': {
80                 label_width: 378,
81                 label_height: 32,
82                 left_margin: 224,
83                 top_margin: 39,
84                 horizontal_gap: 5,
85                 vertical_gap: 4,
86                 number_of_columns: 1,
87                 number_of_rows: 20
88             },
89             '32 Label Size Sticker [1" x 1 1/3"]': {
90                 label_width: 94.5,
91                 label_height: 72,
92                 left_margin: 45,
93                 top_margin: 39,
94                 horizontal_gap: 45,
95                 vertical_gap: 20,
96                 number_of_columns: 4,
97                 number_of_rows: 8
98             },
99             '20 Label Size Sticker [5/6" x 3 3/5"]': {
100                 label_width: 260,
101                 label_height: 60,
102                 left_margin: 30,
103                 top_margin: 36.7,
104                 horizontal_gap: 30,
105                 vertical_gap: 12,
106                 number_of_columns: 2,
107                 number_of_rows: 10
108             }
109         }
113 page_formats["A4 PDF"] = {
114     page_width: 595.3,
115     page_height: 841.9,
116     label_sizes: {
117             'Select a label format' : {},
118             'Custom' : {
119                 label_width: 0,
120                 label_height: 0,
121                 left_margin: 0,
122                 top_margin: 0,
123                 horizontal_gap:0,
124                 vertical_gap:0,
125                 number_of_columns:1,
126                 number_of_rows:1
127             }
128         }
131 page_formats["Zebra printer file"] = {
132     label_sizes: {
133             'Select a label format' : {},
134             '1 1/4" x 2"': {
135                 label_width: 144,
136                 label_height: 90,
137             },
138             'Custom' : {
139                 label_width: 0,
140                 label_height: 0,
141                 left_margin: 0,
142                 top_margin: 0,
143                 horizontal_gap:0,
144                 vertical_gap:0,
145                 number_of_columns:1,
146                 number_of_rows:1
147             }
148         }
151 page_formats["Custom"] = {
152     label_sizes: {
153             'Select a label format' : {},
154             'Custom' : {
155                 label_width: 0,
156                 label_height: 0,
157                 left_margin: 0,
158                 top_margin: 0,
159                 horizontal_gap:0,
160                 vertical_gap:0,
161                 number_of_columns:1,
162                 number_of_rows:1
163             }
164         }
167 var label_options = {};
168 label_options["Select an element type"] = { name: "Select an element type" };
169 label_options["PDFText"] = {
170     name: "Text (PDF)",
171     sizes: {
172         min: 1,
173         max: 144,
174         step: 1,
175         value: 32,
176     },
179 label_options["ZebraText"] = {
180     name: "Text (Zebra)",
181     sizes: {
182         "10": 9,
183         "20": 18,
184         "30": 27,
185         "40": 36,
186         "50": 45,
187         "60": 54,
188         "70": 63,
189         "80": 72
190     }
193 label_options["Code128"] = {
194     name: "1D Barcode (Code128)",
195     sizes: {
196         "One": 1,
197         "Two": 2,
198         "Three": 3,
199         "Four": 4
200     }
203 label_options["QRCode"] = {
204     name: "2D Barcode (QRCode)",
205     sizes: {
206         "Four": 4,
207         "Five": 5,
208         "Six": 6,
209         "Seven": 7,
210         "Eight": 8,
211         "Nine": 9,
212         "Ten": 10
213     }
216 font_styles = {
217     "Courier": "font-family:courier;",
218     "Courier-Bold": "font-family:courier;font-weight:bold;",
219     "Courier-Oblique": "font-family:courier;font-style: oblique;",
220     "Courier-BoldOblique": "font-family:courier;font-weight:bold;font-style: oblique;",
221     "Helvetica": "font-family:helvetica;",
222     "Helvetica-Bold": "font-family:helvetica;font-weight:bold;",
223     "Helvetica-Oblique": "font-family:helvetica;font-style: oblique;",
224     "Helvetica-BoldOblique": "font-family:helvetica;font-weight:bold;font-style: oblique;",
225     "Times": "font-family:times;",
226     "Times-Bold": "font-family:times;font-weight:bold;",
227     "Times-Italic": "font-family:times;font-style: italic;",
228     "Times-BoldItalic": "font-family:times;font-weight:bold;font-style: italic;"
231 var add_fields = {}; // retrieved when data source is selected
232 var num_units = 0; // updated when data source is selected
234 resizer_behaviour = d3.behavior.drag().on(
235     "drag",
236     function(d, i) {
237         var target = d;
238         var bb = getTransGroupBounds(target.node())
239         var mx = d3.event.x;
240         var my = d3.event.y;
241         // if (d3.select("#d3-snapping-check").property("checked")){
242         mx = Math.round(mx / doSnap.size) * doSnap.size
243         my = Math.round(my / doSnap.size) * doSnap.size
244             // }
245         var xexpand = (mx - bb.x) / bb.width
246         var yexpand = (my - bb.y) / bb.height
247         expand = Math.max(xexpand, yexpand)
248         if (!isNaN(expand)) {
249             target.call(doTransform, function(state, selection) {
250                 var expX = state.scale[0] * expand;
251                 var expY = state.scale[1] * expand;
252                 var mi = Math.min(expX * bb.width, expY * bb.height);
253                 if (mi <= 3) {
254                     expX = state.scale[0];
255                     expY = state.scale[1];
256                 }
257                 state.scale[0] = expX;
258                 state.scale[1] = expY;
259             });
260         }
261         var newbb = getTransGroupBounds(target.node())
262         d3.select(this.parentNode).select(".selection-tool-outline")
263             .attr({
264                 x: newbb.x,
265                 y: newbb.y,
266                 width: newbb.width,
267                 height: newbb.height,
268             })
269         d3.select(this.parentNode).select(".selection-tool-resizer")
270             .attr({
271                 x: newbb.x + newbb.width - 3,
272                 y: newbb.y + newbb.height - 3,
273             })
274         d3.select(this.parentNode).select(".selection-tool-remover")
275             .attr({
276                 x: newbb.x - 3,
277                 y: newbb.y - 3,
278             })
279     });
281 //set up drag behaviour
282 var drag_behaviour = d3.behavior.drag().on(
283     "drag",
284     function() {
285         var o = d3.select(this)
286             .call(doTransform, function(state, selection) {
287                 state.translate[0] += d3.event.dx;
288                 state.translate[1] += d3.event.dy;
289             });
290     });
292 $(document).ready(function($) {
294     initializeDrawArea();
295     $('#source_type_select').focus();
297     //Add link to docs
298     // jQuery('#pagetitle_h3').append('&nbsp;<a id="label_designer_docs_link" href="http://solgenomics.github.io/sgn/03_managing_breeding_data/03_12.html"><span class="glyphicon glyphicon-info-sign"></span></a>');
300     // Always focus on autofocus elements when modals are opened.
301     $('.modal').on('shown.bs.modal', function() {
302         $(this).find('[autofocus]').folabel
303     });
305     $('#d3-save-button').click(function() {
306         saveLabelDesign();
307     });
309     $('#design_label_button').click(function() {
310         $("#d3-draw-area").prependTo("#save-labels-display");
311         $(".workflow-complete").click(function() {
312             var title = $(this).children().text();
313             //console.log("workflow element with title "+title+" was just clicked\n");
315             if (title == "Design Your Label") {
316                 $("#d3-draw-area").prependTo("#d3-draw-div");
317             } else if (title == "More Options, Save, And Download") {
318                 $("#d3-draw-area").prependTo("#save-labels-display");
319             }
321         });
322         $("ol.workflow-prog li").click(function() {
323             var title = $(this).children().text();
324             //console.log("workflow element with title "+title+" was just clicked\n");
325             if (title == "More Options, Save, And Download") {
326                 $("#d3-draw-area").prependTo("#save-labels-display");
327             }
329         });
330     });
332     getDataSourceSelect();
334     $("#edit_additional_settings").on("click", function() {
335         $('#editAdditionalSettingsModal').modal('show');
336     });
338     $(document).on("change", "#source_select", function() {
340         var name = jQuery('#source_select :selected').text();
341         jQuery('#selected_data_source').text(name);
343         var data_type = $('#source_select :selected').parent().attr('label');
345         // updateFields(data_type, this.value, '');
347         if (data_type == 'Field Trials') {
348             jQuery.ajax({
349                 url: '/ajax/breeders/trial/'+this.value+'/has_data_levels',
350                 method: 'GET',
351                 beforeSend: function() {
352                     disable_ui();
353                 },
354                 complete: function() {
355                     enable_ui();
356                     jQuery('#page_format').focus();
357                 },
358                 success: function(response) {
359                     var html = '<select class="form-control" id="label_designer_data_level" ><option value="" selected>Select a Level</option><option value="plots">Plot</option>';
360                     if(response.has_plants){
361                         html = html + '<option value="plants">Plant</option>';
362                     }
363                     if(response.has_subplots){
364                         html = html + '<option value="subplots">Subplot</option>';
365                     }
366                     if(response.has_tissue_samples){
367                         html = html + '<option value="tissue_samples">Tissue Sample</option>';
368                     }
369                     html = html + '</select>';
370                     jQuery('#label_designer_data_level_select_div').html(html);
371                     jQuery("#label_designer_data_level").focus();
372                 },
373                 error: function(response) {
374                     alert('There was a problem checking the data levels available for your field trial. Please contact us.');
375                 }
376             });
377         } else if (data_type == 'Genotyping Plates') {
379             jQuery('#label_designer_data_level_select_div').html('<select class="form-control" id="label_designer_data_level" ><option value="" selected>Select a Level</option><option value="plate">Plate</option></select>');
380             jQuery("#label_designer_data_level").focus();
382         } else if (data_type == 'Crossing Experiments') {
384             jQuery('#label_designer_data_level_select_div').html('<select class="form-control" id="label_designer_data_level" ><option value="" selected>Select a Level</option><option value="crosses">Cross</option></select>');
385             jQuery("#label_designer_data_level").focus();
387         } else if ((data_type == 'Lists') || (data_type == 'Public Lists')) {
389             var html = '<select class="form-control" id="label_designer_data_level" ><option value="" selected>Select a Level</option><option value="list">List Items</option>';
390             // Check list type, if Plot, Plant, or Tissue Sample add details option
391             var name = $('#source_select :selected').text();
393             jQuery.ajax({
394                 url: '/list/exists',
395                 data: { 'name': name },
396                 beforeSend: function() {
397                     disable_ui();
398                 },
399                 complete: function() {
400                     enable_ui();
401                     jQuery('#page_format').focus();
402                 },
403                 success: function(response) {
404                     if ( response.list_type === 'trials' ) {
405                         html += '<option value="plots">Plot Details</option>';
406                     } else if (response.list_type == 'plots') {
407                         html = html + '<option value="plots">Plot Details</option>';
408                     } else if (response.list_type == 'subplots') {
409                         html = html + '<option value="subplots">Subplot Details</option>';
410                     } else if (response.list_type == 'plants') {
411                         html = html + '<option value="plants">Plant Details</option>';
412                     } else if (response.list_type == 'tissue_samples') {
413                         html = html + '<option value="tissue_samples">Tissue Sample Details</option>';
414                     } else if (response.list_type == 'crosses') {
415                         html = html + '<option value="crosses">Cross Details</option>';
416                     } else if (response.list_type == 'identifier_generation') {
417                         // remove list item select options and add options for each id batch
418                         html = '<select class="form-control" id="label_designer_data_level" ><option value="" selected>Select a Level</option>';
419                         var lo = new CXGN.List();
420                         var list_data = lo.getListData(response.list_id);
421                         var elements = list_data.elements;
422                         var identifier_object = JSON.parse(elements[0][1]);
423                         var records = identifier_object.records;
424                         for (var x = 0; x < records.length; x++) {
425                             var generated_identifiers = records[x].generated_identifiers;
426                             if (generated_identifiers) {
427                                 //console.log("current identifiers are: "+generated_identifiers);
428                                 var min = generated_identifiers.shift();
429                                 var max = generated_identifiers.pop();
430                                 var id = records[x].next_number;
431                                 html = html + '<option value="batch-'+id+'">ID Batch '+min+' - '+max+'</option>';
432                             }
433                         }
434                     }
435                     html = html + '</select>';
436                     jQuery('#label_designer_data_level_select_div').html(html);
437                     jQuery("#label_designer_data_level").focus();
438                 },
439                 error: function(response) {
440                     alert('There was a problem checking the data levels available for your field trial. Please contact us.');
441                 }
442             });
443         }
444     });
446     jQuery(document).on('change', '#label_designer_data_level', function(){
447         var data_type = $('#source_select :selected').parent().attr('label');
448         var source_id = jQuery('#source_select').val();
450         var name = jQuery('#label_designer_data_level :selected').text();
451         jQuery('#selected_data_level').text(name);
453         if (this.value) { jQuery('#select_datasource_button').prop('disabled', false); }
454         updateFields(data_type, source_id, this.value);
455     });
457     jQuery(document).on('change', 'input[type=radio][name=optradio]', function(){
458         if (this.value == 'saved') {
459             document.getElementById("design_list").style.display = "inline";
460         } else {
461             document.getElementById("design_list").style.display = "none";
462         }
463     });
465     var page_format_select = d3.select("#page_format");
466     page_format_select.on("input", function() {
467         var page = d3.select(this).node().value;
468         if (!page || page == 'Select a page format') {
469             disableDrawArea();
470             d3.select("#label_format").selectAll("option").remove();
471         } else {
472             switchPageDependentOptions(page); // show correct download and text options
473         }
474     });
476     $('#label_format').change(function() {
477         var label = $(this).find('option:selected').val();
478         if (!label || label == 'Select a label format') {
479             disableDrawArea();
480             jQuery('#select_layout_button').prop('disabled',true)
481         } else {
482             switchLabelDependentOptions(label);
483         }
484     });
486     d3.select("#d3-apply-custom-label-size").on("click", function() {
488         //save and apply custom label size
489         jQuery('#select_layout_button').prop('disabled', false)
490         var page = d3.select("#page_format").node().value;
491         var custom_label = page_formats[page].label_sizes['Custom'];
493         custom_label.label_width = document.getElementById("label_width").value;
494         custom_label.label_height = document.getElementById("label_height").value;
495         changeLabelSize(custom_label.label_width, custom_label.label_height);
496         $("#d3-add-and-download-div").removeAttr('style');
497         enableDrawArea();
498         $('#d3-add-type-input').focus();
499     });
501     $('#d3-add-field-input').change(function() {
502         $("#d3-add-size-input").focus();
503     });
505     $("#d3-custom-field").on("click", function() {
506         $("#d3-custom-input").val('');
507         $('#customFieldModal').modal('show');
508     });
510     d3.select("#d3-custom-field-save")
511         .on("click", function() {
512             var custom_text = $("#d3-custom-input").val();
513             var custom_value = custom_text.replace(/\{(.*?)\}/g, function(match, token) {
514                 //console.log("token is "+token);
515                 if (token.match(/Number:/)) {
516                     var parts = token.split(':');
517                     return parts[1];
518                 } else {
519                     return add_fields[token];
520                 }
521             });
523             $("#d3-add-field-input").append($('<option>', {
524                 value: custom_value,
525                 text: custom_text,
526                 selected: "selected"
527             }));
528         });
530     $('#d3-add-type-input').change(function() {
531         if (!this.value || this.value == 'Select an element type') {
532             return;
533         } else {
534             switchTypeDependentOptions(this.value);
535             $("#d3-add-field-input").focus();
536         }
538     });
540     $("#d3-edit-additional-settings").on("click", function() {
541         saveAdditionalOptions(
542             document.getElementById("top_margin").value,
543             document.getElementById("left_margin").value,
544             document.getElementById("horizontal_gap").value,
545             document.getElementById("vertical_gap").value,
546             document.getElementById("number_of_columns").value,
547             document.getElementById("number_of_rows").value,
548             document.getElementById("plot_filter").value,
549             document.getElementById("sort_order_1").value,
550             document.getElementById("sort_order_2").value,
551             document.getElementById("sort_order_3").value,
552             document.getElementById("copies_per_plot").value
553         );
554     });
556     $("#d3-add").on("click", function() {
557         var type = document.getElementById("d3-add-type-input").value;
558         if (!type || type == 'Select an element type') {
559             alert("A valid type must be selected.");
560             return;
561         }
562         var field = $('#d3-add-field-input').find('option:selected').text();
563         if (!field || field == 'Select a field') {
564             alert("A valid field must be selected.");
565             return;
566         }
567         // check if add_fields[field] is defined. If so, add {}s
568         if (add_fields[field]) {
569             field = "{"+field+"}";
570         }
571         var text = document.getElementById("d3-add-field-input").value;
572         var size = document.getElementById("d3-add-size-input").value;
573         var font = document.getElementById("d3-add-font-input").value || 'Courier';
574         addToLabel(field, text, type, size, font);
575         jQuery('#design_label_button').prop('disabled', false)
576     });
578     $("#d3-pdf-button, #d3-zpl-button").on("click", function() {
579         var design = retrievePageParams();
580         var download_type = $(this).val();
581         console.log("Design is "+JSON.stringify(design));
583         // if over 1,000 to download, throw warning with editable start and end number and text recommending to download in batches of 1,000 or less
584         var label_count = num_units * design.copies_per_plot;
585         var labels_to_download = design.labels_to_download;
586         if (label_count < 1000 || (labels_to_download && labels_to_download < 1000)) {
587             downloadLabels(design, download_type);
588         } else if (label_count >= 1000) {
589             //show warning with editable inputs for start and end
590             var message = "You are trying to download "+label_count+ " labels ("+label_count+" "+jQuery('#label_designer_data_level :selected').text()+" x "+design.copies_per_plot+" copy(ies) each). Downloading this many at a time may result in slow speeds. Please use the input boxes below to control how many are downloaded at a time.";
591             $("#batch_download_message").text(message);
592             $("#d3-batch-download-submit").val(download_type);
593             $('#batchDownloadModal').modal('show');
594         }
596     });
598     $("#d3-batch-download-submit").on("click", function() {
599         var download_type = $(this).val();
600         var design = retrievePageParams();
601         downloadLabels(design, download_type);
602     });
606 function downloadLabels (design, download_type) {
607     var label_elements = document.getElementsByClassName('label-element');
608     label_elements = Array.prototype.slice.call(label_elements); // convert to array
609     if (label_elements.length < 1) {
610         alert("No elements in the design. Please add design elements before downloading");
611         return;
612     }
613     var data_type = $('#source_select :selected').parent().attr('label');
614     var source_id = $("#source_select").val();
615     var source_name = $("#source_select :selected").text();
616     //console.log("Id is "+source_id+" and name is "+source_name);
617     if (!source_id || source_id == 'Please select a trial' || source_id == 'Select a plot list') {
618         alert("No data source selected. Please select a data source before downloading");
619         return;
620     }
622     if (!design) {
623         alert("No design. Please define a design before downloading");
624         return;
625     }
626     design.label_elements = label_elements.filter(checkIfVisible).map(getLabelDetails);
628     var design_json = JSON.stringify(design);
629     console.log("design is"+design_json);
630     var data_level = jQuery('#label_designer_data_level').val();
632     //send to server to build pdf file
633     jQuery.ajax({
634         url: '/tools/label_designer/download',
635         timeout: 30000000,
636         method: 'POST',
637         data: {
638             'download_type': download_type,
639             'data_type' : data_type,
640             'source_id': source_id,
641             'source_name': source_name,
642             'design_json': design_json,
643             'data_level': data_level
644         },
645         beforeSend: function() {
646             console.log("Downloading "+download_type+" file . . . ");
647             disable_ui();
648         },
649         complete: function() {
650             enable_ui();
651         },
652         success: function(response) {
653             if (response.error) {
654                 enable_ui();
655                 alert(response.error);
656             } else {
657                 console.log("Got file "+response.filename);
658                 enable_ui();
659                 window.location.href = "/download/" + response.filename;
660             }
661         },
662         error: function(request, status, err) {
663             enable_ui();
664             alert("Error. Unable to download labels.");
665         }
666     });
669 function updateFields(data_type, source_id, data_level){
671     //console.log("running update fields");
672     if (data_type.match(/List/)) {
673         jQuery('#sort_order_1').val('list_order');
674     }
676     jQuery.ajax({
677         url: '/tools/label_designer/retrieve_longest_fields',
678         timeout: 30000000,
679         method: 'POST',
680         data: {
681             data_type: data_type,
682             source_id: source_id,
683             data_level: data_level
684         },
685         beforeSend: function() {
686             disable_ui();
687         },
688         complete: function() {
689             enable_ui();
690             jQuery('#page_format').focus();
691         },
692         success: function(response) {
693             if (response.error) {
694                 alert("An error occured while retrieving the design elements of this trial: " + JSON.stringify(response.error));
695                 getDataSourceSelect();
696             } else {
697                 add_fields = response.fields;
699                 // if reps, add reps as options for filtering
700                 reps = response.reps;
701                 num_units = response.num_units;
702                 addPlotFilter(reps);
703                 addSortOrders(add_fields, data_type, data_level);
704                 createAdders(add_fields);
705                 initializeCustomModal(add_fields);
706                 showLoadOption();
708                 if ( d3.select("#page_format").node().value && d3.select("#page_format").node().value != 'Select a page format') {
709                     switchPageDependentOptions( d3.select("#page_format").node().value );
710                 } else {
711                     var page_format_select = d3.select("#page_format");
712                     page_format_select.selectAll("option")
713                     .data(Object.keys(page_formats))
714                     .enter().append("option")
715                     .text(function(d) {
716                         return d
717                     });
718                 }
719             }
720         },
721         error: function(request, status, err) {
722             alert("Unable to retrieve design elements of this trial. Please confirm this trial has a design, or try again with a different trial. Please contact us!");
723         }
724     });
727 function changeLabelSize(width, height) {
728     var width = width * 2.83 //convert from pixels to dots (72/inch to 8/mm)
729     var height = height * 2.83 //convert from pixels to dots (72/inch to 8/mm)
730     d3.select(".label-template").attr("viewBox", "0 0 " + width + " " + height);
731     d3.select(".d3-bg").attr("width", width);
732     d3.select(".d3-bg").attr("height", height);
733     updateGrid(7);
736 function initializeDrawArea() {
738     //create svg canvas
739     d3.select(".label-template").remove();
740     var svg = d3.select("#d3-draw-area")
741         .append('svg')
742         .classed("label-template", true)
743         .attr({
744             id: "d3-label-area",
745             viewBox: "0 0 510 204"
746         }).classed("d3-draw-svg", true);
748     //set up background
749     var rect = svg.append('rect')
750         .classed("d3-bg", true)
751         .attr({
752             x: 0,
753             y: 0,
754             width: 510,
755             height: 204,
756             fill: "#FFF",
757             stroke: "#D3D3D3",
758             "stroke-width": "2px"
759         })
760         .on("click", clearSelection);
761         svg.append('text')
762         .classed("d3-intro-text", true)
763         .attr({
764             "x": "50%",
765             "y": "38%",
766             "font-size": 30,
767             "text-anchor": "middle",
768             "alignment-baseline": "central",
769         })
770         .text('Label Design Area')
771         svg.append('text')
772         .classed("d3-intro-text", true)
773         .attr({
774             "x": "50%",
775             "y": "55%",
776             "font-size": 16,
777             "style": "font-style: oblique;",
778             "text-anchor": "middle",
779             "alignment-baseline": "central",
780         })
781         .text('Set source, page, and label formats above to start designing.');
783     //set up grid
784     var grid = svg.append("g").classed("d3-bg-grid", true);
785     grid.append('g').classed("d3-bg-grid-vert", true);
786     grid.append('g').classed("d3-bg-grid-horz", true);
787     updateGrid(7);
791 function draggable(d, i) {
792     var bb = this.node().getBBox();
793     this.attr({
794             "s-x": bb.x,
795             "s-y": bb.y
796         })
797         .call(doTransform, function(state, selection) {
798             state.translate = [-bb.x, -bb.y]
799         })
800         .call(drag_behaviour);
803 function selectable(selection, resizeable) {
804     this.on("mousedown", function() {
805         d3.select(".selection-tools").remove();
806     })
807     this.on("click", function() {
808         var o = d3.select(".d3-draw-svg");
809         var bb = getTransGroupBounds(this);
810         var target = d3.select(this);
811         var tools = o.append('g')
812             .classed("selection-tools", true)
813             .datum(target);
814         tools.append("rect")
815             .classed("selection-tool-outline", true)
816             .attr({
817                 x: bb.x,
818                 y: bb.y,
819                 width: bb.width,
820                 height: bb.height,
821                 fill: "none",
822                 stroke: "black",
823                 "stroke-dasharray": ("3,3"),
824                 "stroke-width": "1"
825             })
826         tools.append("rect")
827             .classed("selection-tool-remover", true)
828             .attr({
829                 x: bb.x - 4,
830                 y: bb.y - 4,
831                 width: 8,
832                 height: 8,
833                 fill: "red",
834                 stroke: "none"
835             })
836             .on("click", function() {
837                 d3.event.stopPropagation();
838                 target.remove();
839                 clearSelection();
840             })
841         if (resizeable) {
842             tools.append("rect")
843                 .classed("selection-tool-resizer", true)
844                 .on("click", function() {
845                     d3.event.stopPropagation();
846                 })
847                 .attr({
848                     x: bb.x + bb.width - 4,
849                     y: bb.y + bb.height - 4,
850                     width: 8,
851                     height: 8,
852                     fill: "green",
853                     stroke: "none"
854                 }).call(resizer_behaviour);
855         }
856     })
859 function doTransform(selection, transformFunc) {
860     var state = d3.transform(selection.attr("transform"));
861     transformFunc(state, selection);
862     selection.attr("transform", state.toString());
865 function clearSelection() {
866     d3.select(".selection-tools").remove();
869 function getTransGroupBounds(node) {
870     var bb = node.getBBox()
871     var state = d3.transform(d3.select(node).attr("transform"));
872     bb.x = bb.x * state.scale[0]
873     bb.y = bb.y * state.scale[1]
874     bb.width = bb.width * state.scale[0]
875     bb.height = bb.height * state.scale[1]
876     bb.x += state.translate[0]
877     bb.y += state.translate[1]
878     return bb
881 function updateGrid(size) {
882     //set snapping distance
883     doSnap.size = size;
884     //make x-lines
885     var width = document.getElementById("d3-label-area").viewBox.baseVal.width; //.getBoundingClientRect().width();
886     var height = document.getElementById("d3-label-area").viewBox.baseVal.height;
887     var x_lines = [];
888     for (var x = size; x < width; x += size) {
889         x_lines.push(x);
890     }
891     var vert = d3.select(".d3-bg-grid-vert").selectAll("line")
892         .data(x_lines);
893     vert.exit().remove()
894     vert.enter().append('line')
895     vert.attr({
896             y1: 0,
897             y2: height,
898             stroke: "rgba(0,0,0,0.2)",
899             "stroke-width": 1
900         })
901         .attr("x1", function(d) {
902             return d
903         })
904         .attr("x2", function(d) {
905             return d
906         })
907         .on("click", clearSelection);
908     //make y-lines
909     var y_lines = [];
910     for (var y = size; y < height; y += size) {
911         y_lines.push(y);
912     }
913     var horz = d3.select(".d3-bg-grid-horz").selectAll("line")
914         .data(y_lines);
915     horz.exit().remove()
916     horz.enter().append('line')
917     horz.attr({
918             x1: 0,
919             x2: width,
920             stroke: "rgba(0,0,0,0.2)",
921             "stroke-width": 1
922         })
923         .attr("y1", function(d) {
924             return d
925         })
926         .attr("y2", function(d) {
927             return d
928         })
929         .on("click", clearSelection);
932 function doSnap(state, selection) {
933     var bb = getTransGroupBounds(selection.node());
934     var left_snap_d = (Math.round(bb.x / doSnap.size)) * doSnap.size - bb.x
935     var right_snap_d = (Math.round((bb.x + bb.width) / doSnap.size) * doSnap.size - bb.width) - bb.x
936     var top_snap_d = (Math.round(bb.y / doSnap.size)) * doSnap.size - bb.y
937     var bottom_snap_d = (Math.round((bb.y + bb.height) / doSnap.size) * doSnap.size - bb.height) - bb.y
938     state.translate[0] += Math.abs(left_snap_d) < Math.abs(right_snap_d) ? left_snap_d : right_snap_d
939     state.translate[1] += Math.abs(top_snap_d) < Math.abs(bottom_snap_d) ? top_snap_d : bottom_snap_d
942 function getDataSourceSelect() {
943     get_select_box('label_data_source_types', 'data_source_type', {
944         name: 'source_type_select',
945         id: 'source_type_select',
946         default: 'Select a type of data source for the labels',
947         live_search: 0
948     });
949     updateDataSourceSelect();
950     jQuery(document).off("change").on("change", "#source_type_select", updateDataSourceSelect);
953 function updateDataSourceSelect() {
954     get_select_box('label_data_sources', 'data_source',
955         {
956             name: 'source_select',
957             id: 'source_select',
958             default: 'Select a data source for the labels',
959             live_search: 1,
960             type: jQuery("#source_type_select option:selected").val()
961         }
962     );
965 function switchPageDependentOptions(page) {
966      // load label size and label field options based on page type
967      var label_sizes = page_formats[page].label_sizes;
968      d3.select("#label_format").selectAll("option").remove();
969      d3.select("#label_format").selectAll("option")
970          .data(Object.keys(label_sizes))
971          .enter().append("option")
972          .text(function(d) {
973              return d
974          });
976          d3.select("#d3-add-type-input").selectAll("option").remove();
977          d3.select("#d3-add-type-input").selectAll("option")
978              .data(Object.keys(label_options))
979              .enter().append("option")
980              .text(function(d) {
981                  return label_options[d].name
982              })
983              .attr("value", function(d) {
984                  return d
985              });
987     if (page == 'Zebra printer file') { // disable PDF text option and pdf download, clear pdf text elements
988         $("#d3-add-type-input option[value='PDFText']").remove();
989         switchTypeDependentOptions('ZebraText');
990         document.getElementById("d3-pdf-button").style.display = "none";
991         document.getElementById("d3-zpl-button").style.display = "inline";
992         document.getElementById("d3-add-font-input").value = "Courier"; // default for Zebra
994         var label_elements = document.getElementsByClassName("label-element");
995         label_elements = Array.prototype.slice.call(label_elements); // convert to array
996         for (var i=0; i<label_elements.length; i++) {
997             var element = label_elements[i];
998             if (element.getAttribute("type") != "ZebraText") { // remove all non-Zebra elements, barcodes too in case they have been scaled
999                element.parentNode.removeChild(element);
1000             }
1001         }
1003     } else { // disable Zebra text option and zpl download, clear zpl text elements
1004         switchTypeDependentOptions('PDFText');
1005         $("#d3-add-type-input option[value='ZebraText']").remove();
1006         document.getElementById("d3-zpl-button").style.display = "none";
1007         document.getElementById("d3-pdf-button").style.display = "inline";
1009         var label_elements = document.getElementsByClassName("label-element");
1010         label_elements = Array.prototype.slice.call(label_elements); // convert to array
1011         for (var i=0; i<label_elements.length; i++) {
1012             var element = label_elements[i];
1013              if (element.getAttribute("type") == "ZebraText") { // remove only text, barcodes can stay
1014                 element.parentNode.removeChild(element);
1015              }
1016         }
1017     }
1019     if (page == 'Custom') { // show Custom inputs and attach handlers
1020         document.getElementById("d3-custom-dimensions-div").style.display = "inline";
1021         document.getElementById("d3-page-custom-dimensions-div").style.visibility = "visible";
1022         document.getElementById("d3-label-custom-dimensions-div").style.visibility = "visible";
1023         document.getElementById("label_format").value = 'Custom';
1024         $('#page_width').focus();
1026         $('#page_width').on("change", function() {
1027             page_formats[page].page_width = this.value;
1028         });
1030         $('#page-height').on("change", function() {
1031             page_formats[page].page_height = this.value;
1032         });
1033     } else { //hide page custom input
1034         document.getElementById("d3-page-custom-dimensions-div").style.visibility = "hidden";
1035         $('#label_format').focus();
1036     }
1038     if ( page != 'Custom' && document.getElementById("label_format").value != 'Custom') { //hide all Custom inputs
1039         document.getElementById("d3-custom-dimensions-div").style.display = "none";
1040         document.getElementById("d3-page-custom-dimensions-div").style.visibility = "hidden";
1041         document.getElementById("d3-label-custom-dimensions-div").style.visibility = "hidden";
1042     }
1046 function switchLabelDependentOptions(label) {
1048     var page = d3.select("#page_format").node().value;
1049     var label_sizes = page_formats[page].label_sizes;
1051     if (label == 'Custom') {
1052         document.getElementById("d3-custom-dimensions-div").style.display = "inline";
1053         document.getElementById("d3-label-custom-dimensions-div").style.visibility = "visible";
1054         $('#label_width').focus();
1055     } else {
1056         jQuery('#select_layout_button').prop('disabled', false)
1057         document.getElementById("d3-custom-dimensions-div").style.display = "none";
1058         document.getElementById("d3-label-custom-dimensions-div").style.visibility = "hidden";
1059         changeLabelSize( label_sizes[label].label_width,  label_sizes[label].label_height);
1060         $("#d3-add-and-download-div").removeAttr('style');
1061         $('#d3-add-type-input').focus();
1062         enableDrawArea();
1063     }
1064     //set addtional options
1065     document.getElementById("top_margin").value = label_sizes[label].top_margin;
1066     document.getElementById("left_margin").value = label_sizes[label].left_margin;
1067     document.getElementById("horizontal_gap").value = label_sizes[label].horizontal_gap;
1068     document.getElementById("vertical_gap").value = label_sizes[label].vertical_gap;
1069     document.getElementById("number_of_columns").value = label_sizes[label].number_of_columns;
1070     document.getElementById("number_of_rows").value = label_sizes[label].number_of_rows;
1073 function switchTypeDependentOptions(type){
1075     var sizes = label_options[type].sizes;
1076     if (type == "PDFText") {
1078         // set up font select
1079         d3.select("#d3-add-font-input").selectAll("option").remove();
1080         d3.select("#d3-add-font-input").selectAll("option")
1081             .data(Object.keys(font_styles).sort())
1082             .enter().append("option")
1083             .text(function(d) {
1084                 return d
1085             })
1086             .attr("style", function(d) {
1087                 return font_styles[d]
1088             })
1089             .attr("value", function(d) {
1090                 return d
1091             });
1092         document.getElementById("d3-add-font-div").style.visibility = "visible";
1094         // set up size input and slider
1095         $("#d3-add-size-input").replaceWith('<input type="number" id="d3-add-size-input" class="form-control"></input>');
1096         var size_input = d3.select("#d3-add-size-input");
1097         var size_slider = d3.select("#d3-add-size-slider");
1098         size_input.attr(sizes);
1099         size_slider.attr(sizes);
1100         size_slider.on("input", function() {
1101             size_input.property("value", this.value)
1102         });
1103         size_input.on("change", function() {
1104             size_slider.node().value = this.value;
1105         });
1106         $("#d3-add-size-slider").show();
1108     } else {
1109         document.getElementById("d3-add-font-div").style.visibility = "hidden";
1110         $("#d3-add-size-input").replaceWith('<select id="d3-add-size-input" class="form-control"></select>&nbsp&nbsp');
1111         d3.select("#d3-add-size-input").selectAll("option")
1112             .data(Object.keys(sizes))
1113             .enter().append("option")
1114             .text(function(d) {
1115                 return d
1116             })
1117             .attr("value", function(d) {
1118                 return sizes[d]
1119             });
1120         $("#d3-add-size-slider").hide();
1121     }
1124 function addToLabel(field, text, type, size, font, x, y, width, height) {
1125      //console.log("Field is: "+field+" and text is: "+text+" and type is: "+type+" and size is: "+size+" and font is: "+font);
1126     svg = d3.select(".d3-draw-svg");
1128     //get x,y coords and scale
1129     if ((typeof x || typeof y ) === 'undefined') {
1130         x = document.getElementById("d3-label-area").viewBox.baseVal.width/2;
1131         y = document.getElementById("d3-label-area").viewBox.baseVal.height/2;
1132     }
1133     //console.log(" X is: "+x+" and y is: "+y);
1135     //count existing elements
1136     var label_elements = document.getElementsByClassName('label-element');
1137     label_elements = Array.prototype.slice.call(label_elements); // convert to array
1138     var count = label_elements.length;
1140     //set up new element
1141     var new_element = svg.append("g")
1142         .classed("draggable", true)
1143         .classed("selectable", true)
1144         .call(draggable)
1146     switch (type) {
1147         case "Code128":
1148         case "QRCode":
1149             //console.log("Adding barcode of type "+type);
1150             //add barcode specific attributes
1151             disable_ui();
1152             var page = d3.select("#page_format").node().value;
1153             new_element.classed("barcode", true)
1154             if ( page.match(/Zebra/) ) {
1155                 new_element.call(selectable, false);
1156             } else {
1157                 new_element.call(selectable, true);
1158             }
1159             var img = new Image();
1160             img.src = "/tools/label_designer/preview?content=" + encodeURIComponent(text) + "&type=" + encodeURIComponent(type) + "&size=" + encodeURIComponent(size);
1161             img.onload = function() {
1162                 var element_width = width || this.width;
1163                 var element_height = height || this.height;
1164                 x = x - (element_width /2);
1165                 y = y - (element_height /2);
1166                 //console.log("Final x is "+x+" and final y is "+y);
1167                 new_element.call(doTransform, function(state, selection) {
1168                     state.translate = [x,y]
1169                 }, doSnap)
1170                 .append("svg:image")
1171                 .attr({
1172                     "id": "element"+count,
1173                     "class": "label-element",
1174                     "value": field,
1175                     "size": size,
1176                     "type": type,
1177                     "width": element_width,
1178                     "height": element_height,
1179                     "href": "/tools/label_designer/preview?content=" + encodeURIComponent(text) + "&type=" + encodeURIComponent(type) + "&size=" + encodeURIComponent(size),
1180                 });
1181                 enable_ui();
1182             }
1183             break;
1185         default:
1186             //add text specific attributes
1187             //console.log("Adding text of type "+type);
1188             var font_size = parseInt(size);
1189             if (type =="ZebraText") {
1190                 font_size = font_size + parseInt(font_size/9);
1191             }
1192             new_element.classed("text-box", true)
1193             .call(selectable, false)
1194             .call(doTransform, function(state, selection) {
1195                 state.translate = [x,y]
1196             })
1197             .append("text")
1198             .attr({
1199                 "id": "element"+count,
1200                 "class": "label-element",
1201                 "value": field,
1202                 "size": size,
1203                 "type": type,
1204                 "font-size": font_size,
1205                 "font": font,
1206                 "style": font_styles[font],
1207                 "text-anchor": "middle",
1208                 "alignment-baseline": "middle",
1209             })
1210             .text(text)
1211             //console.log("Field is: "+field+" and size is: "+size+" and type is: "+type+" and font size is: "+font_size+" and font is: "+font+" and style is: "+font_styles[font]+" and text is: "+text);
1213             break;
1214     }
1217 function saveAdditionalOptions(top_margin, left_margin, horizontal_gap, vertical_gap, number_of_columns, number_of_rows, plot_filter, sort_order_1, sort_order_2, sort_order_3, copies_per_plot) {
1218     // save options in javascript object and in html elements
1219     var page = d3.select("#page_format").node().value;
1220     var label = d3.select("#label_format").node().value;
1221     page_formats[page].label_sizes[label].top_margin = top_margin;
1222     page_formats[page].label_sizes[label].left_margin = left_margin;
1223     page_formats[page].label_sizes[label].horizontal_gap = horizontal_gap;
1224     page_formats[page].label_sizes[label].vertical_gap = vertical_gap;
1225     page_formats[page].label_sizes[label].number_of_columns = number_of_columns;
1226     page_formats[page].label_sizes[label].number_of_rows = number_of_rows;
1227     page_formats[page].label_sizes[label].plot_filter = plot_filter;
1228     page_formats[page].label_sizes[label].sort_order_1 = sort_order_1;
1229     page_formats[page].label_sizes[label].sort_order_2 = sort_order_2;
1230     page_formats[page].label_sizes[label].sort_order_3 = sort_order_3;
1231     page_formats[page].label_sizes[label].copies_per_plot = copies_per_plot;
1232     document.getElementById("top_margin").value = top_margin;
1233     document.getElementById("left_margin").value = left_margin;
1234     document.getElementById("horizontal_gap").value = horizontal_gap;
1235     document.getElementById("vertical_gap").value = vertical_gap;
1236     document.getElementById("number_of_columns").value = number_of_columns;
1237     document.getElementById("number_of_rows").value = number_of_rows;
1238     document.getElementById("plot_filter").value = plot_filter || 'all';
1239     document.getElementById("sort_order_1").value = sort_order_1;
1240     document.getElementById("sort_order_2").value = sort_order_2;
1241     document.getElementById("sort_order_3").value = sort_order_3;
1242     document.getElementById("copies_per_plot").value = copies_per_plot;
1245 function dragSnap() {
1246     d3.select(this).call(doTransform, doSnap);
1249 function createAdders(add_fields) {
1250     // load type select options
1251     d3.select("#d3-add-type-input").selectAll("option").remove();
1252     d3.select("#d3-add-type-input").selectAll("option")
1253         .data(Object.keys(label_options))
1254         .enter().append("option")
1255         .text(function(d) {
1256             return label_options[d].name
1257         })
1258         .attr("value", function(d) {
1259             return d
1260         });
1262     //load field options
1263     d3.select("#d3-add-field-input").selectAll("option").remove();
1264     d3.select("#d3-add-field-input").selectAll("option")
1265         .data(["Select a field", ...Object.keys(add_fields).sort()])
1266         .enter().append("option")
1267         .text(function(d) {
1268             return d
1269         })
1270         .attr("value", function(d) {
1271             return add_fields[d]
1272         });
1276 function addPlotFilter(reps) {
1278     Object.keys(reps).forEach(function(key) {
1279         var newkey = "Rep "+key+" only";
1280         reps[newkey] = key;
1281         delete reps[key];
1282     });
1283     reps['All'] = 'all';
1285     d3.select("#plot_filter").selectAll("option").remove();
1286     d3.select("#plot_filter").selectAll("option")
1287         .data(Object.keys(reps).sort())
1288         .enter().append("option")
1289         .text(function(d) {
1290             return d
1291         })
1292         .attr("value", function(d) {
1293             return reps[d]
1294         });
1298 function addSortOrders(add_fields, data_type, data_level) {
1300     // Set type-specific sorting options
1301     let data_type_fields = [];
1302     // Sort by trial layout for plot-level labels...
1303     if ( ['Field Trials', 'Lists', 'Public Lists'].includes(data_type) && data_level === 'plots' ) {
1304         data_type_fields = ['Trial Layout: Plot Order'];
1305     }
1307     //load options
1308     d3.selectAll("#sort_order_1, #sort_order_2, #sort_order_3").selectAll("option").remove();
1309     d3.selectAll("#sort_order_1").selectAll("option")
1310         .data(["Select a field", ...data_type_fields, ...Object.keys(add_fields).sort()])
1311         .enter().append("option")
1312         .text(function(d) {
1313             return d
1314         })
1315         .attr("value", function(d) {
1316             return d
1317         });
1318     d3.selectAll("#sort_order_2, #sort_order_3").selectAll("option")
1319         .data(["Select a field", ...Object.keys(add_fields).sort()])
1320         .enter().append("option")
1321         .text(function(d) {
1322             return d
1323         })
1324         .attr("value", function(d) {
1325             return d
1326         });
1327     jQuery("#sort_order_1").off("change").on("change", () => {
1328         const sel = jQuery("#sort_order_1").val();
1329         if ( sel === 'Select a field' ) {
1330             jQuery("#sort_order_2_container, #sort_order_3_container").hide();
1331             jQuery("#sort_order_layout_order_container, #sort_order_layout_start_container").hide();
1332         }
1333         else if ( sel === 'Trial Layout: Plot Order' ) {
1334             jQuery("#sort_order_2_container, #sort_order_3_container").hide();
1335             jQuery("#sort_order_layout_order_container, #sort_order_layout_start_container").show();
1336         }
1337         else {
1338             jQuery("#sort_order_2_container").show();
1339             jQuery("#sort_order_layout_order_container, #sort_order_layout_start_container").hide();
1340         }
1341     });
1342     jQuery("#sort_order_2").off("change").on("change", () => {
1343         const sel = jQuery("#sort_order_2").val();
1344         if ( sel === 'Select a field' ) {
1345             jQuery("#sort_order_3_container").hide();
1346         }
1347         else {
1348             jQuery("#sort_order_3_container").show();
1349         }
1350     });
1353 function checkIfVisible(element) {
1354     var label_width = document.getElementById("d3-label-area").viewBox.baseVal.width;
1355     var label_height = document.getElementById("d3-label-area").viewBox.baseVal.height;
1356     var transform_attributes = parseTransform(element.parentNode.getAttribute('transform')); // return transform attributes as an object
1357     var coords = transform_attributes.translate;
1358     var type = element.getAttribute("type");
1359     if (coords[0] > label_width || coords[1] > label_height) { // ignore elements not visible in draw area
1360         return false; // skip
1361     } else {
1362         return true;
1363     }
1366 function getLabelDetails(element) {
1367     var transform_attributes = parseTransform(element.parentNode.getAttribute('transform')); // return transform attributes as an object
1368     //console.log("Transform attributes are: "+JSON.stringify(transform_attributes));
1369     var coords = transform_attributes.translate;
1370     var scale = transform_attributes.scale || new Array(1,1);
1371     var rect = element.getBBox();
1372     var width = rect.width * scale[0];
1373     var height = rect.height * scale[1];
1374     //console.log("Height is: "+height+" and width is: "+width);
1375     var type = element.getAttribute("type");
1376     var x;
1377     var y;
1378     if (type.match(/Text/)) {
1379         x = parseInt(coords[0])
1380         y = parseInt(coords[1])
1381     } else {
1382         x = parseInt(coords[0]) + (width/2);
1383         y = parseInt(coords[1]) + (height/2);
1384     }
1386     return {
1387         x: x,
1388         y: y,
1389         height: height,
1390         width: width,
1391         value: element.getAttribute("value"),
1392         type: element.getAttribute("type"),
1393         font: element.getAttribute("font") || 'Courier',
1394         size: element.getAttribute("size")
1395     };
1398 function parseTransform(transform) {
1399     var attribute_object = {};
1400     var attributes = transform.split(')');
1402     for (var i = 0; i < attributes.length; i++) {
1403         var attribute = attributes[i];
1404         var parts = attribute.split('(');
1405         var name = parts.shift();
1406         var values = parts.join(',');
1407         attribute_object[name] = values.split(',');
1408     }
1410     return attribute_object;
1413 function retrievePageParams() {
1415     var page = d3.select("#page_format").node().value;
1416     if (!page || page == 'Select a page format') {
1417         alert("No page format select. Please select a page format");
1418         return;
1419     }
1420     var label = d3.select("#label_format").node().value;
1421     if (!label || label == 'Select a label format') {
1422         alert("No label format select. Please select a label format");
1423         return;
1424     }
1425     var label_sizes = page_formats[page].label_sizes;
1427     var page_params = {
1428         page_format: page,
1429         page_width: page_formats[page].page_width || document.getElementById("page_width").value,
1430         page_height: page_formats[page].page_height || document.getElementById("page_height").value,
1431         left_margin: label_sizes[label].left_margin,
1432         top_margin: label_sizes[label].top_margin,
1433         horizontal_gap: label_sizes[label].horizontal_gap,
1434         vertical_gap: label_sizes[label].vertical_gap,
1435         number_of_columns: label_sizes[label].number_of_columns,
1436         number_of_rows: label_sizes[label].number_of_rows,
1437         plot_filter: document.getElementById("plot_filter").value,
1438         sort_order_1: document.getElementById("sort_order_1").value,
1439         sort_order_2: document.getElementById("sort_order_2").value,
1440         sort_order_3: document.getElementById("sort_order_3").value,
1441         sort_order_layout_order: document.getElementById("sort_order_layout_order").value,
1442         sort_order_layout_start: document.getElementById("sort_order_layout_start").value,
1443         copies_per_plot: document.getElementById("copies_per_plot").value,
1444         labels_to_download: document.getElementById("label_designer_labels_to_download").value,
1445         start_number: document.getElementById("label_designer_start_number").value,
1446         end_number: document.getElementById("label_designer_end_number").value,
1447         label_format: label,
1448         label_width: label_sizes[label].label_width,
1449         label_height: label_sizes[label].label_height,
1450         start_col: document.getElementById("start_col").value,
1451         start_row: document.getElementById("start_row").value,
1452     }
1453     return page_params;
1457 function initializeCustomModal(add_fields) {
1458     //load field options
1459     d3.select("#d3-custom-add-field-input").selectAll("option").remove();
1460     d3.select("#d3-custom-add-field-input").selectAll("option")
1461         .data(["Select a field", ...Object.keys(add_fields).sort()])
1462         .enter().append("option")
1463         .text(function(d) {
1464             return d
1465         })
1466         .attr("value", function(d) {
1467             return add_fields[d]
1468         });
1470     $('#d3-custom-add-field-input').on("change", function() {
1472         var value = $(this).find('option:selected').text();
1473         if (!value || value == 'Select a field') {
1474             return;
1475         } else {
1476             var custom_field = $("#d3-custom-input").val() + "{" + value + "}";
1477             $("#d3-custom-input").val(custom_field);
1478         }
1480     });
1482     $("#d3-custom-preview").on("click", function() {
1483         var custom_field = $("#d3-custom-input").val();
1484         var result = custom_field.replace(/\{(.*?)\}/g, function(match, token) {
1485             console.log("token is "+token);
1486             if (token.match(/Number:/)) {
1487                 var parts = token.split(':');
1488                 return parts[1];
1489             } else {
1490                 return add_fields[token];
1491             }
1492         });
1493         $("#d3-custom-content").text(result);
1494     });
1496     $("#d3-add-number").on("click", function() {
1497         var custom_field = $("#d3-custom-input").val() + "{Number:" + $('#start_number').val() +":"+ $('#increment_number').val()+"}";
1498         $("#d3-custom-input").val(custom_field);
1499     });
1503 function enableDrawArea() {
1504     var intro_elements = document.getElementsByClassName("d3-intro-text");
1505     for (var i=0; i<intro_elements.length; i++) { intro_elements[i].style.display = "none"; }
1506     var label_elements = document.getElementsByClassName("label-element");
1507     for(var i=0; i<label_elements.length; i++) { label_elements[i].style.display = "inline"; }
1510 function disableDrawArea() {
1511     var intro_elements = document.getElementsByClassName("d3-intro-text");
1512     for (var i=0; i<intro_elements.length; i++) { intro_elements[i].style.display = "inline"; }
1513     var label_elements = document.getElementsByClassName("label-element");
1514     for(var i=0; i<label_elements.length; i++) { label_elements[i].style.display = "none"; }
1517 function saveLabelDesign() {
1518     var label_elements = document.getElementsByClassName('label-element');
1519     label_elements = Array.prototype.slice.call(label_elements); // convert to array
1520     if (label_elements.length < 1) {
1521         alert("No elements in the design. Please add design elements before saving");
1522         return;
1523     }
1525     var lo = new CXGN.List();
1526     var new_name = $('#save_design_name').val();
1527     page_params = JSON.stringify(retrievePageParams());
1529     if (!page_params) {return;}
1530     var data = page_params.slice(1, -1).split(",").join("\n"); // get key value pairs in list format
1531     label_params = label_elements.filter(checkIfVisible).map(getLabelDetails);
1533     for (i=0; i < label_params.length; i++) { // add numbered element key for each set of label params
1534         data += '\n"element'+i+'": '+JSON.stringify(label_params[i]);
1535     }
1537     list_id = lo.newList(new_name);
1538     if (list_id > 0) {
1539         var elementsAdded = lo.addToList(list_id, data);
1540         lo.setListType(list_id, 'label_design');
1541     }
1542     if (elementsAdded) {
1543         alert("Saved label design with name " + new_name);
1544     }
1547 function fillInPlaceholders(match, placeholder) { // replace placeholders with actual values
1548         var filled = add_fields[placeholder];
1549         // console.log("Filling "+placeholder+" with "+filled);
1550         if (typeof filled === 'undefined' && !placeholder.match(/Number:/)) {
1551             // console.log(placeholder+" is undefined. Alerting with warning");
1552             alert("Missing field. Your selected design includes the field "+placeholder+" which is not available from the selected data source. Please pick a different saved design or data source, or remove the undefined field from the design area.")
1553         }
1554         return filled;
1557 function showLoadOption() {
1558     // document.getElementById('design_label').style.display = "inline";
1559     // document.getElementById('design_list').style.display = "inline";
1560     var lo = new CXGN.List();
1561     $('#design_list').html(lo.listSelect('design_list', ['label_design'], 'Select a saved design', 'refresh', undefined));
1562     $('#design_list_list_select').change(
1563       function() {
1564         Workflow.complete(this);
1565         loadDesign(this.value);
1566         jQuery('#design_label_button').prop('disabled', false);
1567     });
1570 function loadDesign (list_id) {
1571     // console.log("Loading design from list with ID "+list_id);
1572     // clear existing draw area
1573     initializeDrawArea();
1575     var lo = new CXGN.List();
1576     var list_data = lo.getListData(list_id);
1577     var elements = list_data.elements;
1579     //parse into javascript object
1580     var fixed_elements = Object.values(elements).map(function(e){ return e.pop(); });
1581     var params = JSON.parse("{"+fixed_elements.join(',')+"}");
1583     for (var key in params) {
1584         if (key.match(/element/)) {
1585             var element_obj = params[key];
1586             var value = element_obj.value;
1587             var text = value.replace(/\{(.*?)\}/g, fillInPlaceholders);
1588             //console.log("Width is "+element_obj.width);
1589             addToLabel(value, text, element_obj.type, element_obj.size, element_obj.font, element_obj.x, element_obj.y, element_obj.width, element_obj.height);
1590         }
1591     }
1593     var page = params['page_format'];
1594     document.getElementById('page_format').value = page;
1595     //console.log("page is "+page);
1596     switchPageDependentOptions(page);
1597     if (page == 'Custom') {
1598         document.getElementById("page_width").value = params['page_width'];
1599         document.getElementById("page_height").value = params['page_height'];
1600     }
1602     var label = params['label_format'];
1603     document.getElementById('label_format').value = label;
1604     switchLabelDependentOptions(label);
1605     if (label == 'Custom') {
1606         document.getElementById("label_width").value = params['label_width'];
1607         document.getElementById("label_height").value = params['label_height'];
1608         page_formats[page].label_sizes['Custom'].label_width = params['label_width'];
1609         page_formats[page].label_sizes['Custom'].label_height = params['label_height'];
1610         changeLabelSize(params['label_width'], params['label_height']);
1611         $("#d3-add-and-download-div").removeAttr('style');
1612         enableDrawArea();
1613     }
1615     saveAdditionalOptions(
1616         params['top_margin'],
1617         params['left_margin'],
1618         params['horizontal_gap'],
1619         params['vertical_gap'],
1620         params['number_of_columns'],
1621         params['number_of_rows'],
1622         params['plot_filter'],
1623         params['sort_order_1'],
1624         params['sort_order_2'],
1625         params['sort_order_3'],
1626         params['copies_per_plot']
1627     );