· 6 years ago · Nov 18, 2019, 09:32 PM
1/*
2 Invokables is used mainly for the add sample modal used in the Gradalis workflow
3
4 - This file handles the overlay creation and deletion from the client.
5 - It also will handle the requests sent to create the samples in invokables.py
6
7 */
8
9// global variables
10let sample_type_definitions = [], sample_tree = [], vigil_data = [];
11let sample_types_allowed, add_samples_to_worksheet_bool, add_dependency_bool;
12let gridOptions;
13let SPECIAL_COLUMNS = ["Sample #", "Barcode"];
14
15// Custom Sample Mapping
16let SAMPLE_MAPPING = {
17 "XVIVBOT": "Gent-Bottle",
18 "HSAALIQ": "Aliquot",
19 "HSARETA": "- Retain",
20 "HSABOTT": "- Solution",
21 "PRCELID": "Cell Identification",
22 "PRTXIMM": "D.1 ELISPOT",
23 "PRTXRET": "Pre-Tx/Irr",
24 "PRTXCUL": "D.1 Production Culture",
25 "Blank": "Pleur",
26 "TRMRETA": "Transport Media",
27 "PRTXWAR": "D.1 Wash Media",
28 "POTXIMM": "D.2 ELISPOT",
29 "MYCOPLA": "D.2 Mycoplasma",
30 "MYCOSUP": "IM-Sup.",
31 "POTXRET": "Post-Tx/Irr",
32 "POTXCUL": "D.2 Production Culture",
33 "POTXWAR": "D.2 Wash Media",
34 "FFIFA37": "iAST - 37",
35 "FFIFA30": "iAST - 30",
36 "FFIFN37": "iNST - 37",
37 "VGLVIAL": "Final Product Vial",
38 "RSCHFR": "Research Sample Fraction",
39 "RESPRTX": "Pre-TX",
40 "RESPRIR": "Pre-IRR",
41 "RESPOIR": "Post-IRR",
42 "RESPOTT": "Sample Potency Targeting",
43 "RESVIAL": "Research Sample Final",
44 "TRMEIFA": "iFA",
45 "TRMEIFN": "iFN",
46 "XVIVRET": "GentRetain"
47};
48
49// Vigil Data
50let lot_number, subject_id;
51
52class SampleModal {
53
54 static __version__ = "0.0.1";
55
56 constructor(header, footer, content) {
57
58 /*
59
60 SampleModal is used to create the sample modal UI for the user to create, add barcodes, and sample meta data.
61 Each function is also a static method in case the user only wants part of a modal instead to be constructed.
62
63
64 Arguments:
65 header_element (HTMLDivElement): What element you would like to be in the header
66 footer_element (HTMLDivElement): What elements you would like to be placed into the footer.
67 content (HTMLDivElement): The content you would like to supply into the modal.
68
69 */
70
71 this._overlay_element = SampleModal.overlay();
72 this._container_element = SampleModal.container();
73 this._header_element = SampleModal.header(header);
74 this._content_element = SampleModal.modal_content(content);
75 this._create_button_element = SampleModal.create_sample_button();
76 this._cancel_button_element = SampleModal.cancel_button();
77 this._footer_element = SampleModal.footer(footer);
78
79 if (!this._overlay_element || !this._container_element || !this._header_element || !this._content_element ||
80 !this._footer_element || !this._create_button_element || !this._cancel_button_element) {
81 console.log("SampleModal arguments are not satisfied to create the modal")
82 } else {
83 /* Construct the modal */
84 this._create_modal()
85 }
86
87 }
88
89 static overlay () {
90
91 let overlay = document.createElement('div');
92 overlay.className = 'fas-modal-overlay';
93 overlay.style['background-color'] = 'rgba(black, 0.25)';
94 overlay.style['height'] = "100vh";
95 overlay.style['width'] = "100vw";
96 overlay.style['position'] = "fixed";
97 overlay.style['top'] = "0";
98 overlay.style['left'] = "0";
99 overlay.style['display'] = "flex";
100 overlay.style['align-items'] = "center";
101 overlay.style['justify-content'] = "center";
102 overlay.style['pointer-events'] = "all";
103 overlay.style['z-index'] = "1000";
104
105 return overlay
106 }
107
108 static container () {
109
110 /* Container */
111 let container = document.createElement('div');
112 container.style['background-color'] = 'white';
113 container.style['border-radius'] = '2px';
114 container.style['height'] = "50vh";
115 container.style['width'] = "50vw";
116 container.style.boxShadow = '0px 0px 4px rgba(0,0,0, 0.7)';
117 container.style['border-top'] = '6px solid #0e76ba';
118 container.style['display'] = 'flex';
119 container.style['flex-direction'] = 'column';
120 container.style['margin-top'] = '0';
121 container.style['min-width'] = '440px';
122 container.style['padding'] = '0';
123 container.style['position'] = 'relative';
124 container.style['transition'] = 'all 300ms ease-out';
125
126 return container
127 }
128
129 static header (header_element) {
130
131 /*
132
133 Creates the header
134
135 Arguments:
136 header_element (HTMLDivElement): What element you would like to be in the header
137
138 */
139
140 /* Header Wrapper */
141 let headerWrap = document.createElement('div');
142 headerWrap.className = 'fas-header';
143 headerWrap.style['display'] = 'flex';
144 headerWrap.style['flex-direction'] = '1';
145 headerWrap.style['justify-content'] = 'space-between';
146 headerWrap.style['align-items'] = 'center';
147 headerWrap.style['font-weight'] = '600';
148 headerWrap.style['padding'] = '16px 20px';
149 headerWrap.style['min-height'] = '56px';
150
151 /* Header Content */
152 let headerContent = document.createElement('div');
153 headerContent.className = 'fas-header--content';
154 headerContent.style.flex = '1';
155 headerContent.innerHTML = header_element;
156
157 /* Close Button */
158 let headerActions = document.createElement('div');
159 let closeButton = document.createElement('i');
160 closeButton.innerText='close';
161 closeButton.className = 'esp-icons close';
162 closeButton.onclick=SampleModal.destroy_modal;
163 closeButton.style['font-size'] = '24px';
164 closeButton.style['cursor'] = 'pointer';
165 closeButton.style['color'] = '#5f5f5f';
166 closeButton.style['font-style'] = 'normal';
167 closeButton.style['font-family'] = "'esp-font' !important";
168
169 headerWrap.appendChild(headerContent);
170 headerActions.appendChild(closeButton);
171 headerWrap.appendChild(headerActions);
172
173 return headerWrap
174 }
175
176 static modal_content (content) {
177
178 /*
179
180 Embeds the content
181
182 Arguments:
183 content (HTMLDivElement): The content you would like to supply into the modal.
184
185 Returns:
186 contentWrapper (HTMLDivElement): ContentWrapper div element with the content placed inside
187
188 */
189
190 let contentWrapper = document.createElement('div');
191 contentWrapper.className = 'fas-modal-content';
192 contentWrapper.style['padding'] = '0 20px 20px';
193 contentWrapper.style['height'] = '100%';
194 contentWrapper.style['flex'] = '1';
195 contentWrapper.style['max-height'] = 'calc(100vh - 160px)';
196 contentWrapper.style['overflow'] = 'auto';
197 contentWrapper.innerHTML=content;
198 return contentWrapper
199
200 }
201
202 static create_sample_button () {
203 /*
204
205 Returns:
206 button (HTMLbuttonElement): button element is returned.
207
208 */
209
210 let create_button = document.createElement('button');
211 create_button.className = 'btn primary';
212 create_button.innerHTML = '<div class="wrapper"><div class="label">Create Samples</div></div>';
213 create_button.onclick = function() {
214 if (!SampleModal.active_modal) {
215 return
216 }
217
218 let worksheet = api.getSampleSheetUuid();
219 let count = document.getElementById('bartender_count').value;
220 let sample_type_name = document.getElementById('bartender_printer').value;
221
222 // First pass checks
223 if (!sample_type_name || !count) {
224 api.showNotification('Please specify Sample Type, and Amount.', 'error');
225 return
226 } else {
227 document.getElementById('bartender_messages').innerText = ''
228 }
229
230 // Fetch Ag-grid Data
231 var sample_data = [];
232
233 let rows_displayed_count = gridOptions.api.getDisplayedRowCount();
234
235 function _check_duplicates(array) {
236 return new Set(array).size !== array.length;
237 }
238
239 // Check the barcodes within the the modal itself first
240 let barcodes_displayed = [];
241 for (var i = 0; i < rows_displayed_count; i++) {
242 let rowNode = gridOptions.api.getDisplayedRowAtIndex(i);
243 barcodes_displayed.push(rowNode.data.Barcode);
244 }
245
246 if (_check_duplicates(barcodes_displayed)) {
247 api.showNotification('Barcode is not Unique', 'error');
248 return
249 }
250
251 for (var i = 0; i < rows_displayed_count; i++) {
252 let rowNode = gridOptions.api.getDisplayedRowAtIndex(i);
253 let sample_meta = [];
254 for (var j = 0; j < gridOptions.columnDefs.length; j++) {
255
256 let col_header = gridOptions.columnDefs[j].headerName;
257
258 if (SPECIAL_COLUMNS.includes(col_header)) {
259 continue
260 }
261
262 let col_data = rowNode.data[col_header];
263 if (col_data === undefined) {
264 col_data = ""
265 }
266 sample_meta.push(
267 {
268 "col_data": col_data,
269 "col_header": col_header
270 }
271 )
272 }
273
274 // Check with the graveyard
275 var barcode_is_not_unique = SampleModal.evaluate_barcode(rowNode.data.Barcode);
276 if (!barcode_is_not_unique) {
277 sample_data.push(
278 {
279 "sample_number": i,
280 "sample_barcode": rowNode.data.Barcode,
281 "sample_meta": sample_meta
282 }
283 )
284 } else {
285 api.showNotification('Barcode is not Unique', 'error');
286 return
287 }
288
289 }
290
291 // Prepare the payload
292 let instance_payload = {
293 "sample_sheet_uuid": worksheet,
294 "sample_type_name": sample_type_name,
295 "sample_number": count.toString(),
296 "sample_data": sample_data,
297 "add_samples_to_worksheet": add_samples_to_worksheet_bool.toString(),
298 "add_dependency": add_dependency_bool.toString()
299 };
300
301 // Send the request over the the invokables.
302 // Makes it easier to handle in the python layer the actual talking to the ORM to make the updates.
303 api.getFromESP(
304 '/api/invoke/add_samples_to_worksheet?kwargs=' + JSON.stringify(instance_payload),
305 function(results) {
306 api.showNotification("Samples Added", "success");
307 api.refetchTab()
308 },
309 function(error) {
310 console.log("Invokable: add_samples_to_worksheet error has occurred. Stacktrace: " +error.toString());
311 api.showNotification("An error has occurred, please contact the FAS Team.", "error")
312 }
313 );
314
315 SampleModal.destroy_modal()
316 }
317 create_button.style['background-color'] = '#0e76ba';
318 create_button.style['font-family'] = 'Open Sans';
319 create_button.style['border-color'] = '#0e76ba';
320 create_button.style['color'] = '#ffffff';
321 create_button.style['font-size'] = '13px';
322 create_button.style['font-weight'] = '600';
323 create_button.style['border'] = '1px solid';
324 create_button.style['vertical-align'] = 'middle';
325 create_button.style['cursor'] = 'pointer';
326 create_button.style['text-align'] = 'center';
327 create_button.style['min-height'] = '26px';
328 create_button.style['border-radius'] = '2px';
329
330 return create_button
331
332 }
333
334 static cancel_button () {
335
336 /*
337
338 Returns:
339 button (HTMLbuttonElement): create button element is returned.
340
341 */
342
343 let cancel_button = document.createElement('button');
344 cancel_button.style['font-family'] = 'Open Sans';
345 cancel_button.style['background-color'] = '#ffffff';
346 cancel_button.style['border-color'] = '#979797';
347 cancel_button.style['color'] = '#484848';
348 cancel_button.style['font-size'] = '13px';
349 cancel_button.style['font-weight'] = '600';
350 cancel_button.style['border'] = '1px solid';
351 cancel_button.style['vertical-align'] = 'middle';
352 cancel_button.style['cursor'] = 'pointer';
353 cancel_button.style['text-align'] = 'center';
354 cancel_button.style['min-height'] = '26px';
355 cancel_button.style['border-radius'] = '2px';
356 cancel_button.innerHTML = '<div class="wrapper"><div class="label">Cancel</div></div>'
357 cancel_button.className = 'btn secondary';
358 cancel_button.onclick = function() {
359 SampleModal.destroy_modal()
360 };
361
362 return cancel_button
363 }
364
365 static footer (footer_elements) {
366
367 /*
368
369 Creates the footer
370
371 Arguments:
372 footer_element (HTMLDivElement): What elements you would like to be placed into the footer.
373
374 Returns:
375 footerWrapper (HTMLDivElement): footerWrapper div element with the footeer elements placed inside
376
377 */
378
379 let footerWrapper = document.createElement('div');
380 footerWrapper.innerHTML = footer_elements;
381 footerWrapper.style['display'] = 'flex';
382 footerWrapper.style['flex-direction'] = 'row';
383 footerWrapper.style['justify-content'] = 'flex-end';
384 footerWrapper.style['align-items'] = 'center';
385 footerWrapper.style['padding'] = '16px 20px';
386 footerWrapper.style['border-top'] = '1px solid #d8d8d8';
387 return footerWrapper
388 }
389
390 _create_modal () {
391 this._body = document.getElementsByTagName('body')[0];
392 this._overlay_element.appendChild(this._container_element);
393 this._container_element.appendChild(this._header_element);
394 this._container_element.appendChild(this._content_element);
395 this._footer_element.appendChild(this._cancel_button_element);
396 this._footer_element.appendChild(this._create_button_element);
397 this._container_element.appendChild(this._footer_element);
398 this._body.appendChild(this._overlay_element);
399
400 }
401
402 static destroy_modal () {
403
404 /*
405
406 Removes the modal overlay
407
408 Arguments:
409 api (Object): Client Validation API Object
410 */
411 let active_modal = this.active_modal;
412 if (active_modal) {
413 active_modal.parentNode.removeChild(active_modal);
414 } else {
415 console.log("No active modal")
416 }
417 }
418
419 static get active_modal () {
420
421 /*
422
423 Retrieves the active modal overlay
424
425 */
426
427 return document.querySelector('.fas-modal-overlay')
428 }
429
430 static create_table () {
431
432 // Set Licensing
433 const key = "L7_Informatics_Enterprise_Science_Platform_3Devs_5OEM_8_August_2019__MTU2NTIxODgwMDAwMA==02c676ec74743c60bbd1471e0c9fed9f";
434 agGrid.LicenseManager.setLicenseKey(key);
435
436 // Fetch values from the modal
437 let count = document.getElementById('bartender_count').value;
438 let sample_type_name = document.getElementById('bartender_printer').value;
439
440 // Handle any errors
441 if (!sample_type_name || !count) {
442 document.getElementById('bartender_messages').innerText = 'Please specify Sample Type, and Amount.'
443 return
444 } else {
445 document.getElementById('bartender_messages').innerText = ''
446 }
447
448 // Declare ag-grid variables
449 let columnDefs = [
450 {headerName: "Sample #", field: "Sample #"},
451 {headerName: "Barcode", field: "Barcode", editable: true, valueSetter: function (params) {
452 if (!params.newValue.includes(subject_id)) {
453 api.showNotification("Barcode must contain the correct subject id",'error');
454 return false
455 } else if (!params.newValue.includes(lot_number)) {
456 api.showNotification("Barcode must contain the correct lot number",'error');
457 return false
458 }
459 params.data[params.colDef.field] = params.newValue;
460 return true
461 }},
462 ];
463
464 let gridDivElement = document.querySelector('#gridDiv');
465
466 // Append Sample Type Definitions columns
467 for (var i = 0; i < sample_type_definitions.length; i++) {
468 if (sample_type_definitions[i]["name"] === sample_type_name && sample_type_definitions[i]["resource_vars"].length > 0) {
469 for (var j = 0; j < sample_type_definitions[i]["resource_vars"].length; j++) {
470
471 var sample_type_column = {};
472 sample_type_column["headerName"] = sample_type_definitions[i]["resource_vars"][j]["name"];
473 sample_type_column["field"] = sample_type_definitions[i]["resource_vars"][j]["name"];
474
475 // Set the column to editable
476 if (sample_type_definitions[i]["resource_vars"][j]["read_only"] === false) {
477 sample_type_column["editable"] = true
478 }
479
480 // Numeric Column Handling & Validation
481 if (sample_type_definitions[i]["resource_vars"][j]["var_type"] === "numeric") {
482 sample_type_column["valueSetter"] = function(params) {
483 if (!isNaN(parseFloat(params.newValue))) {
484 params.data[params.colDef.field] = params.newValue;
485 return true;
486 }
487 return false;
488 }
489 }
490
491 // Date Column Handling & Validation
492 if (sample_type_definitions[i]["resource_vars"][j]["var_type"] === "date") {
493 sample_type_column["cellEditor"] = SampleModal.create_date_picker();
494
495 }
496 columnDefs.push(sample_type_column)
497 }
498 }
499 }
500
501 // Set the ag-grid grid options
502 gridOptions = {
503 columnDefs: columnDefs,
504 animateRows: true,
505 enterMovesDownAfterEdit: true,
506 enterMovesDown: true,
507 components:{
508 agDateInput: SampleModal.create_date_picker()
509 },
510 };
511
512 // Draw the table
513 new agGrid.Grid(gridDivElement, gridOptions);
514
515
516 // Fill in the table with data
517 let rowData = [];
518 for (var i = 0; i < parseInt(count); i++) {
519 var row = {};
520 row["Sample #"] = i + 1;
521 row["Barcode"] = "";
522 row["SIN"] = subject_id;
523 row["Lot Number"] = lot_number;
524 rowData.push(row);
525 }
526
527 gridOptions.api.setRowData(rowData);
528
529 }
530
531 static destroy_table () {
532
533 /**
534
535 This function destroys the active gridDiv element in the model overlay
536
537 */
538
539 let gridDivElement = document.querySelector('#gridDiv');
540 gridDivElement.innerHTML = "";
541
542 }
543
544 static evaluate_barcode (barcode) {
545
546 /**
547
548 This function will evaluate the data within the ag-grid, if anything is wrong it will spit the message
549 back to the user and deny the create samples request.
550
551 */
552
553 // Fetch the barcode graveyard data (Already handled when the modal is created (have it happen async on page load)
554 // works better than nesting within the sample modal.
555
556 // Make sure the barcode graveyard actually exists on the vigil lot.
557 let barcode_graveyard_exists = sample_tree.results[0].meta.labels === undefined;
558
559 if (barcode_graveyard_exists) {
560 document.getElementById('bartender_messages').innerText = 'Vigil Lot does not contain a' +
561 ' graveyard, please contact an L7 Representative';
562 return false
563 }
564
565 let barcode_graveyard = sample_tree.results[0].meta.labels;
566
567 return barcode_graveyard.includes(barcode);
568
569 }
570
571 static create_date_picker () {
572
573 /**
574
575 Create the Datepicker renderer inside the cell using the jQuery UI standard datepicker
576
577 */
578
579 let valid_date = true;
580
581 function CustomDateComponent() {
582 }
583
584
585
586 CustomDateComponent.prototype.init = function (params) {
587
588 let custom_date_component = this;
589 var template =
590 "<input type='text' data-input />" +
591 "<a class='input-button' title='clear' data-clear>" +
592 "<i class='fa fa-times'></i>" +
593 "</a>";
594
595 this.params = params;
596
597 this.eGui = document.createElement('div');
598
599 var eGui = this.eGui;
600
601 eGui.setAttribute('role', 'presentation');
602 eGui.classList.add('ag-input-wrapper');
603 eGui.classList.add('custom-date-filter');
604 eGui.innerHTML = template;
605
606 this.eInput = eGui.querySelector('input');
607
608 this.picker = flatpickr(this.eGui, {
609 onChange: function (dateobj, datestr) {
610 custom_date_component.date = datestr;
611 },
612 dateFormat: 'm/d/Y',
613 wrap: true,
614 enableTime: true,
615 minDate: "today"
616 });
617
618 this.picker.calendarContainer.classList.add('ag-custom-component-popup');
619
620 this.date = null;
621 };
622
623 CustomDateComponent.prototype.getGui = function () {
624 return this.eGui;
625 };
626
627
628 CustomDateComponent.prototype.getDate = function () {
629 return this.date;
630 };
631
632 CustomDateComponent.prototype.setDate = function (date) {
633 this.picker.setDate(date);
634 this.date = date;
635 };
636
637 return CustomDateComponent
638
639 }
640
641}
642
643let load_data = function () {
644 load_sample_types();
645 load_flatpickr();
646 load_ancestor_tree(api.getSampleUuidAt(0));
647 load_vigil_data(api.getSampleUuidAt(0))
648};
649
650let load_flatpickr = function () {
651
652 /**
653
654 Load the flatpickr JS library
655
656 */
657
658 // Load JS
659 $.ajax({
660 url: '/static/js/flatpickr/flatpickr.js',
661 type: 'GET',
662 async: true,
663 success: function(data) {},
664 error: function(error) {
665 console.log(error)
666 }
667 });
668
669 // Load CSS
670 var document_head = document.getElementsByTagName('head')[0];
671 var flatpickr_link_element = document.createElement('link');
672 flatpickr_link_element.rel = 'stylesheet';
673 flatpickr_link_element.type = 'text/css';
674 flatpickr_link_element.href = '/static/js/flatpickr/flatpickr.css';
675 document_head.append(flatpickr_link_element);
676
677};
678
679let load_sample_types = function () {
680
681 /**
682
683 Fetch all sample types definitions in the system
684
685 */
686 $.ajax({
687 url: '/api/sample_types',
688 type: 'GET',
689 async: true,
690 success: function(data) {
691 sample_type_definitions = data
692 },
693 error: function(error) {
694 console.log(error)
695 }
696 })
697};
698
699let load_ancestor_tree = function (sample_uuid, target_type = 'Vigil Lot') {
700
701 /**
702
703 Fetch the Ancestor Tree for the sample, for Gradalis specifically geared for Vigil Lot.
704
705 Arguments:
706 sample_uuid (String): root sample_uuid
707 target_type (String): Name of the root sample you are looking for.
708
709 Returns:
710 data (Array): Array of the root sample used to search the query and the sample object data within that list.
711 */
712
713 let data = {
714 uuids: [sample_uuid],
715 targettype: [target_type]
716 };
717
718 $.ajax({
719 method: "POST",
720 url: "/api/v2/queries/furthest_sample_ancestors_of_type",
721 contentType:"application/json; charset=utf-8",
722 dataType: "json",
723 async: true,
724 data: JSON.stringify(data),
725 success: function (data) {
726 sample_tree = data;
727 console.log(sample_tree)
728 },
729 error: function (error) {
730 console.log(error);
731 },
732 processData: false
733 });
734};
735
736let load_vigil_data = function(sample_uuid) {
737 let data = {
738 uuid: sample_uuid,
739 };
740
741 $.ajax({
742 method: "POST",
743 url: "/api/v2/queries/fetch_process_data_by_sample",
744 contentType:"application/json; charset=utf-8",
745 dataType: "json",
746 async: true,
747 data: JSON.stringify(data),
748 success: function (data) {
749 vigil_data = data.results;
750
751 for (var i = 0; i < vigil_data.length; i++) {
752 if (vigil_data[i].columnname === 'Lot Number') {
753 lot_number = vigil_data[i].columnvalue
754 }
755 if (vigil_data[i].columnname === 'Subject ID') {
756 subject_id = vigil_data[i].columnvalue
757 }
758 }
759 },
760 error: function (error) {
761 console.log(error);
762 },
763 processData: false
764 });
765};
766
767let load_sample_modal = function () {
768 /* Create the button */
769 let button = api.addProtocolButton('Add a Sample');
770 let content = `
771 <div class="form-field">
772 <label for="bartender_template" class="data__label">Add a Sample</label>
773 </div>
774 <div class="form-field">
775 <label for="bartender_printer" class="data__label">Sample Type</label>
776 <div class="form-field__input">
777 <select id="bartender_printer" class="select-input" name="bartender_printer">
778 <option value="">Select a Sample Type</option>
779 {{ SAMPLE_TYPE_OPTIONS }}
780 </select>
781 </div>
782 </div>
783 <div class="form-field">
784 <label for='bartender_count' class="data__label"># Samples</label>
785 <div class="form-field__input">
786 <input type="text" size="4" id="bartender_count" name="bartender_count" class="form-input"/>
787 </div>
788 </div>
789 <div id="gridDiv" style="height:500px;width:100%;" class="ag-theme-balham">
790 </div>
791 <div id="bartender_messages" style="color: red">
792 </div>
793 `;
794
795 let header = "<div>Sample Configuration</div>";
796 let option_list = '';
797
798 sample_types_allowed.forEach(option => {
799 option_list += `<option value="${option}">${option}</option>`
800 });
801
802 content = content.replace('{{ SAMPLE_TYPE_OPTIONS }}', option_list);
803
804 button.setOnClick(function () {
805 new SampleModal(header, "", content);
806 document.getElementById("bartender_count").onchange = function () {
807
808 // Destroy the active table
809 SampleModal.destroy_table();
810
811 // Create a new one
812 SampleModal.create_table();
813 }
814 })
815
816};
817
818/* Main Invokable for adding the sample modal */
819function add_sample_modal(api, sample_types = [] , add_samples_to_worksheet = false, add_dependency = false) {
820
821 /**
822
823 Adds the sample modal
824
825 Arguments:
826 api (Object): ClientValidationAPI Object
827 sample_types_allowed (Array): Sample types allowed on the protocol
828
829
830 */
831
832 sample_types_allowed = sample_types;
833 add_samples_to_worksheet_bool = add_samples_to_worksheet;
834 add_dependency_bool = add_dependency;
835
836 let scriptjQuery = document.createElement('script');
837 let scriptGrid = document.createElement('script');
838 let body = document.body;
839
840 scriptjQuery.src = "/static/js/jquery-2.1.0.js";
841 scriptjQuery.onload = load_data;
842 scriptjQuery.onreadystatechange = load_data;
843
844 scriptGrid.src = "/static/js/ag-grid-enterprise.min.js";
845 scriptGrid.onload = load_sample_modal;
846 scriptGrid.onreadystatechange = load_sample_modal;
847
848 body.appendChild(scriptjQuery);
849 body.appendChild(scriptGrid);
850
851}