· 6 years ago · Oct 29, 2019, 08:38 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
9class SampleModal {
10 constructor(content, gridOptions) {
11
12 /*
13
14 Handle the overlay variable declarations initially as part of the constructor object.
15
16 Arguments:
17
18
19 */
20
21 this._overlay_element = SampleModal.overlay()
22 this._container_element = SampleModal.container()
23 this._header_element = SampleModal.header()
24 this._footer = SampleModal.header()
25
26 }
27
28 static overlay () {
29
30 let overlay = document.createElement('div')
31 overlay.className = 'fas-modal-overlay'
32 overlay.style['background-color'] = 'rgba(black, 0.25)'
33 overlay.style['height'] = "100vh"
34 overlay.style['width'] = "100vw"
35 overlay.style['position'] = "fixed"
36 overlay.style['top'] = "0"
37 overlay.style['left'] = "0"
38 overlay.style['display'] = "flex"
39 overlay.style['align-items'] = "center"
40 overlay.style['justify-content'] = "center"
41 overlay.style['pointer-events'] = "all"
42 overlay.style['z-index'] = "1000"
43
44 return overlay
45 }
46
47 static container () {
48
49 /* Container */
50 let container = document.createElement('div')
51 container.style['background-color'] = 'white'
52 container.style['border-radius'] = '2px'
53 container.style.boxShadow = '0px 0px 4px rgba(0,0,0, 0.7)'
54 container.style['border-top'] = '6px solid #0e76ba'
55 container.style['display'] = 'flex'
56 container.style['flex-direction'] = 'column'
57 container.style['margin-top'] = '0'
58 container.style['min-width'] = '440px'
59 container.style['padding'] = '0'
60 container.style['position'] = 'relative'
61 container.style['transition'] = 'all 300ms ease-out'
62
63 return container
64 }
65
66 static header () {
67
68 /* Header Wrapper */
69 let headerWrap = document.createElement('div')
70 headerWrap.className = 'fas-header'
71 headerWrap.style['display'] = 'flex'
72 headerWrap.style['flex-direction'] = '1'
73 headerWrap.style['justify-content'] = 'space-between'
74 headerWrap.style['align-items'] = 'center'
75 headerWrap.style['font-weight'] = '600'
76 headerWrap.style['padding'] = '16px 20px'
77 headerWrap.style['min-height'] = '56px'
78
79 /* Header Content */
80 let headerContent = document.createElement('div')
81 headerContent.className = 'fas-header--content'
82 headerContent.style.flex = '1'
83 headerContent.innerHTML = "<div>Sample Configuration</div>"
84
85 /* Close Button */
86 let headerActions = document.createElement('div')
87 let closeButton = document.createElement('i')
88 closeButton.innerText='close'
89 closeButton.className = 'esp-icons close'
90 closeButton.onclick=destroy_modal
91 closeButton.style['font-size'] = '24px'
92 closeButton.style['cursor'] = 'pointer'
93 closeButton.style['color'] = '#5f5f5f'
94 closeButton.style['font-style'] = 'normal'
95 closeButton.style['font-family'] = "'esp-font' !important"
96
97 headerWrap.appendChild(headerContent)
98 headerActions.appendChild(closeButton)
99 headerWrap.appendChild(headerActions)
100
101 return headerWrap
102 }
103
104 static footer () {
105
106 let footerWrapper = document.createElement('div')
107 footerWrapper.innerHTML =
108
109 footerWrapper.style['display'] = 'flex'
110 footerWrapper.style['flex-direction'] = 'row'
111 footerWrapper.style['justify-content'] = 'flex-end'
112 footerWrapper.style['align-items'] = 'center'
113 footerWrapper.style['padding'] = '16px 20px'
114 footerWrapper.style['border-top'] = '1px solid #d8d8d8'
115 return footerWrapper
116 }
117}
118function _modal_overlay() {
119 let overlay = document.createElement('div')
120 overlay.className = 'fas-modal-overlay'
121 overlay.style['background-color'] = 'rgba(black, 0.25)'
122 overlay.style['height'] = "100vh"
123 overlay.style['width'] = "100vw"
124 overlay.style['position'] = "fixed"
125 overlay.style['top'] = "0"
126 overlay.style['left'] = "0"
127 overlay.style['display'] = "flex"
128 overlay.style['align-items'] = "center"
129 overlay.style['justify-content'] = "center"
130 overlay.style['pointer-events'] = "all"
131 overlay.style['z-index'] = "1000"
132 return overlay
133}
134
135function _modal_container() {
136 let container = document.createElement('div')
137 container.style['background-color'] = 'white'
138 container.style['border-radius'] = '2px'
139 container.style.boxShadow = '0px 0px 4px rgba(0,0,0, 0.7)'
140 container.style['border-top'] = '6px solid #0e76ba'
141 container.style['display'] = 'flex'
142 container.style['flex-direction'] = 'column'
143 container.style['margin-top'] = '0'
144 container.style['min-width'] = '440px'
145 container.style['padding'] = '0'
146 container.style['position'] = 'relative'
147 container.style['transition'] = 'all 300ms ease-out'
148 return container
149}
150
151function _modal_header(header) {
152 let headerWrap = document.createElement('div')
153 headerWrap.className = 'fas-header'
154
155 headerWrap.style['display'] = 'flex'
156 headerWrap.style['flex-direction'] = '1'
157 headerWrap.style['justify-content'] = 'space-between'
158 headerWrap.style['align-items'] = 'center'
159 headerWrap.style['font-weight'] = '600'
160 headerWrap.style['padding'] = '16px 20px'
161 headerWrap.style['min-height'] = '56px'
162
163 let headerCont = document.createElement('div')
164 headerCont.className = 'fas-header--content'
165 headerCont.style.flex = '1'
166
167 headerWrap.appendChild(headerCont)
168
169 if (header) {
170 if (typeof(header) === 'string') {
171 headerCont.innerHTML = header
172 } else {
173 headerCont.appendChild(header)
174 }
175 }
176 let header_actions = document.createElement('div')
177 let close = document.createElement('i')
178 close.innerText='close'
179 close.className = 'esp-icons close'
180 close.onclick=destroy_modal
181 close.style['font-size'] = '24px'
182 close.style['cursor'] = 'pointer'
183 close.style['color'] = '#5f5f5f'
184 close.style['font-style'] = 'normal'
185 close.style['font-family'] = "'esp-font' !important"
186
187 header_actions.appendChild(close)
188
189 headerWrap.appendChild(header_actions)
190
191 return headerWrap
192}
193
194function _modal_content(content) {
195 let contentWrapper = document.createElement('div')
196 contentWrapper.className = 'fas-modal-content'
197 contentWrapper.style['padding'] = '0 20px 20px'
198 contentWrapper.style['height'] = '100%'
199 contentWrapper.style['flex'] = '1'
200 contentWrapper.style['max-height'] = 'calc(100vh - 160px)'
201 contentWrapper.style['overflow'] = 'auto'
202 if (typeof(content) === "string") {
203 contentWrapper.innerHTML=content
204 } else {
205 contentWrapper.appendChild(content)
206 }
207 return contentWrapper
208}
209
210function _modal_footer(footer) {
211 if (!footer) {
212 return null
213 }
214 let footerWrapper = document.createElement('div')
215 if (typeof(footer) === "string") {
216 footerWrapper.innerHTML = footer
217 } else {
218 footerWrapper.appendChild(footer)
219 }
220
221 footerWrapper.style['display'] = 'flex'
222 footerWrapper.style['flex-direction'] = 'row'
223 footerWrapper.style['justify-content'] = 'flex-end'
224 footerWrapper.style['align-items'] = 'center'
225 footerWrapper.style['padding'] = '16px 20px'
226 footerWrapper.style['border-top'] = '1px solid #d8d8d8'
227 return footerWrapper
228}
229
230/* TODO: api should have a create_modal.*/
231function create_modal(content, header, footer, gridOptions) {
232 // styling borrowed from main application to try to
233 // make it fit in ok.
234 let overlay = _modal_overlay()
235 let container = _modal_container()
236 overlay.appendChild(container)
237 let headerWrapper = _modal_header(header)
238 container.appendChild(headerWrapper)
239
240 let contentWrapper = _modal_content(content)
241 container.appendChild(contentWrapper)
242
243 let footerWrapper = _modal_footer(footer)
244 if (footerWrapper) {
245 container.appendChild(footerWrapper)
246 }
247
248 let body = document.getElementsByTagName('body')[0]
249 body.appendChild(overlay);
250 add_aggrid_onclick(gridOptions)
251}
252
253function create_ag_grid_table(gridDiv, gridOptions) {
254 let rowData = [];
255 let count = document.getElementById('bartender_count').value
256 let sample_type_name = document.getElementById('bartender_printer').value
257 if (!sample_type_name || !count) {
258 document.getElementById('bartender_messages').innerText = 'Please specify Sample Type, and Amount.'
259 return
260 } else {
261 document.getElementById('bartender_messages').innerText = ''
262 }
263
264 new agGrid.Grid(gridDiv, gridOptions);
265
266 for (var i = 0; i < sample_type_definitions.length; i++) {
267 if (sample_type_definitions[i]["name"] === sample_type_name && sample_type_definitions[i]["resource_vars"].length > 0) {
268 for (var j = 0; i < sample_type_definitions[i]["resource_vars"].length; j++) {
269 gridOptions.columnDefs.push({headerName: sample_type_definitions[i]["resource_vars"][j]["name"], field: sample_type_definitions[i]["resource_vars"][j]["name"]})
270 }
271 }
272 }
273
274 console.log(gridOptions.columnDefs)
275
276 for (var i = 0; i < parseInt(count); i++) {
277 var row = {}
278 row["Sample #"] = i + 1;
279 row["Barcode"] = "";
280 rowData.push(row);
281 }
282 gridOptions.api.setRowData(rowData);
283}
284function active_modal() {
285 return document.querySelector('.fas-modal-overlay')
286}
287
288function destroy_modal(api) {
289 let modal = active_modal()
290 if (modal) {
291 modal.parentNode.removeChild(modal)
292 }
293 api.removeVMSaving()
294}
295
296function add_aggrid_onclick (gridOptions) {
297 var agGridButton = document.querySelector('#ag-grid-button')
298 agGridButton.onclick = function () {
299 var gridDiv = document.querySelector('#gridDiv');
300 create_ag_grid_table(gridDiv, gridOptions)
301 }
302}
303
304var loadJS = function(url, implementationCode, location){
305 //url is URL of external file, implementationCode is the code
306 //to be called from the file, location is the location to
307 //insert the <script> element
308
309 var scriptTag = document.createElement('script');
310 scriptTag.src = url;
311
312 scriptTag.onload = implementationCode;
313 scriptTag.onreadystatechange = implementationCode;
314
315 location.appendChild(scriptTag);
316};
317
318var sample_type_definitions = [];
319
320var sample_type_definitons = async function() {
321 $.ajax({
322 url: '/api/sample_type_definitions',
323 type: 'GET',
324 success: function(data) {
325 sample_type_definitions = data
326 },
327 error: function(error) {
328 console.log(error)
329 }
330 })
331};
332
333var modal = function(){
334 let content = `
335 <div class="form-field">
336 <label for="bartender_template" class="data__label">Add a Sample</label>
337 </div>
338 <div class="form-field">
339 <label for="bartender_printer" class="data__label">Sample Type</label>
340 <div class="form-field__input">
341 <select id="bartender_printer" class="select-input" name="bartender_printer">
342 <option value="">Select a Sample Type</option>
343 <option value="Material">Material</option>
344 </select>
345 </div>
346 </div>
347 <div class="form-field">
348 <label for='bartender_count' class="data__label"># Samples</label>
349 <div class="form-field__input">
350 <input type="text" size="4" id="bartender_count" name="bartender_count" class="form-input"/>
351 </div>
352 </div>
353 <div id="gridDiv" style="height:500px;width:100%;" class="ag-theme-balham">
354 </div>
355 <div id="bartender_messages" style="color: red">
356 </div>
357 `
358
359 let columnDefs = [
360 {headerName: "Sample #", field: "Sample #"},
361 {headerName: "Barcode", field: "Barcode", editable: true},
362 ];
363
364 var gridOptions = {
365 columnDefs: columnDefs,
366 enableCellChangeFlash: true,
367 gridAutoHeight: true,
368 animateRows: true,
369 floatingFilter: true
370 };
371
372 const key = "L7_Informatics_Enterprise_Science_Platform_3Devs_5OEM_8_August_2019__MTU2NTIxODgwMDAwMA==02c676ec74743c60bbd1471e0c9fed9f";
373 agGrid.LicenseManager.setLicenseKey(key);
374
375 var agGridButton = document.createElement('button');
376 agGridButton.innerHTML = '<div class="wrapper"><div class="label">Create Table</div></div>'
377 agGridButton.className = 'btn primary'
378 agGridButton.id = 'ag-grid-button'
379
380 agGridButton.style['background-color'] = '#0e76ba'
381 agGridButton.style['font-family'] = 'Open Sans'
382 agGridButton.style['border-color'] = '#0e76ba'
383 agGridButton.style['color'] = '#ffffff'
384 agGridButton.style['font-size'] = '13px'
385 agGridButton.style['font-weight'] = '600'
386 agGridButton.style['border'] = '1px solid'
387 agGridButton.style['vertical-align'] = 'middle'
388 agGridButton.style['cursor'] = 'pointer'
389 agGridButton.style['text-align'] = 'center'
390 agGridButton.style['min-height'] = '26px'
391 agGridButton.style['border-radius'] = '2px'
392
393 let contentDiv = document.createElement('div')
394 contentDiv.innerHTML= content
395 contentDiv.appendChild(agGridButton)
396
397 let footerDiv = document.createElement('div')
398 let b2 = document.createElement('button')
399 b2.className = 'btn primary'
400 b2.innerHTML = '<div class="wrapper"><div class="label">Create Samples</div></div>'
401 b2.onclick = function() {
402 if (!active_modal()) {
403 return
404 }
405 let worksheet = api.getSampleSheetUuid()
406 let count = document.getElementById('bartender_count').value
407 let sample_type_name = document.getElementById('bartender_printer').value
408 if (!sample_type_name || !count) {
409 document.getElementById('bartender_messages').innerText = 'Please specify Sample Type, and Amount.'
410 return
411 } else {
412 document.getElementById('bartender_messages').innerText = ''
413 }
414
415 var instance_payload = {"sample_sheet_uuid": worksheet, "sample_type_name": sample_type_name, "sample_number": count};
416
417 api.getFromESP(
418 '/api/invoke/add_samples_to_worksheet?kwargs=' + JSON.stringify(instance_payload),
419 function(results) {
420 api.showNotification("Samples Added", "success")
421 api.refetchTab()
422 },
423 function(error) {
424 console.log("Invokable: add_samples_to_worksheet error has occured. Stacktrace: " +error.toString());
425 api.showNotification("An error has occured, please contact the FAS Team.", "error")
426 }
427 );
428
429 destroy_modal(api)
430 }
431 b2.style['background-color'] = '#0e76ba'
432 b2.style['font-family'] = 'Open Sans'
433 b2.style['border-color'] = '#0e76ba'
434 b2.style['color'] = '#ffffff'
435 b2.style['font-size'] = '13px'
436 b2.style['font-weight'] = '600'
437 b2.style['border'] = '1px solid'
438 b2.style['vertical-align'] = 'middle'
439 b2.style['cursor'] = 'pointer'
440 b2.style['text-align'] = 'center'
441 b2.style['min-height'] = '26px'
442 b2.style['border-radius'] = '2px'
443
444 let b3 = document.createElement('button')
445 b3.style['font-family'] = 'Open Sans'
446 b3.style['background-color'] = '#ffffff'
447 b3.style['border-color'] = '#979797'
448 b3.style['color'] = '#484848'
449 b3.style['font-size'] = '13px'
450 b3.style['font-weight'] = '600'
451 b3.style['border'] = '1px solid'
452 b3.style['vertical-align'] = 'middle'
453 b3.style['cursor'] = 'pointer'
454 b3.style['text-align'] = 'center'
455 b3.style['min-height'] = '26px'
456 b3.style['border-radius'] = '2px'
457 b3.innerHTML = '<div class="wrapper"><div class="label">Cancel</div></div>'
458 b3.className = 'btn secondary'
459 b3.onclick = function() {
460 destroy_modal(api)
461 }
462 footerDiv.appendChild(b3)
463 //append save after cancel for better consistency with reset of
464 //UI, where Cancel is first.
465 footerDiv.appendChild(b2)
466 create_modal(contentDiv, headerDiv, footerDiv, gridOptions)
467}
468
469function add_sample_modal(api) {
470 let button = api.addProtocolButton('Add a Sample');
471 loadJS('/static/js/jquery-2.1.0.js', sample_type_definitons, document.body);
472 button.setOnClick(function() {
473 loadJS('/static/js/ag-grid-enterprise.min.js', modal, document.body);
474 })
475}