MIDAS
Loading...
Searching...
No Matches
eqtable.js
Go to the documentation of this file.
1let eqtable_style = `
2.mtable {
3 border-spacing: 0;
4}
5.mvarheader {
6 border-bottom-left-radius: 0;
7 border-bottom-right-radius: 0;
8 border-right: 1px solid white;
9}
10.mtable td {
11 padding: 5px;
12 border-right: 1px solid white;
13}
14.mtable th {
15 padding: 5px;
16 text-align: left;
17}
18.mthselect {
19 color:white;
20 font-weight:bold;
21 text-align-last:center;
22 border: none;
23 outline: none;
24 background-color: transparent;
25 width:100%;
26}
27td.pageSelector {
28 padding: 5px;
29 border-left: 2px solid #808080;
30 border-right: 2px solid #808080;
31 border-top: 2px solid #808080;
32 border-radius: 5px 5px 0 0;
33}
34`;
35
36let estyle = document.createElement('style');
37estyle.textContent = eqtable_style;
38document.head.appendChild(estyle);
39
40<!-- Select file dialog -->
41let dlgFileSelect = `
42<div class="dlgTitlebar">Select file</div>
43<div class="dlgPanel">
44 <br />
45 <div style="margin: auto;width: 70%">
46 <input type="file" id="fileSelector" accept=".json">
47 </div>
48 <br /><br />
49 <button class="dlgButton" onclick="importFileFromSelector(this.parentNode.parentNode);dlgHide('dlgFileSelect');">Load</button>
50 <button class="dlgButton" onclick="dlgHide('dlgFileSelect')">Cancel</button>
51</div>
52`;
53
54let equipment = {
55 "name" : "",
56 "enabled": [],
57 "tid": "",
58 "table" : [],
59 "vars": [],
60 "group": [],
61 "frontend_name": ""
62};
63
64function check_frontend() {
65
66 let fename = equipment.frontend_name;
67 mjsonrpc_call("cm_exist", {"name": fename}).then( function(rpc) {
68 if (rpc.result.status === 1)
69 document.getElementById("fewarning").style.display = "none";
70 else {
71 document.getElementById("fewarning").style.display = "";
72 document.getElementById("fewarning").firstChild.innerHTML =
73 "<a href='?cmd=Programs'>" + fename + "</a> not running";
74 }
75 window.setTimeout(check_frontend, 5000);
76 }).catch(function(error){mjsonrpc_error_alert(error);});
77
78}
79
80function valueChanged(t) {
81
82 let td = t.parentElement;
83
84 // don't make extra display yellow
85 if (td.parentElement &&
86 td.parentElement.parentElement &&
87 td.parentElement.parentElement.parentElement &&
88 td.parentElement.parentElement.parentElement.parentElement &&
89 td.parentElement.parentElement.parentElement.parentElement.id === "displayExtra")
90 return;
91
92 // check if value really changed
93 if (t.oldValue === undefined) {
94 t.oldValue = t.innerText;
95 return;
96 }
97
98 if (t.innerText === t.oldValue)
99 return;
100 t.oldValue = t.innerText;
101
102 td.style.backgroundColor = 'var(--myellow)';
103 td.style.setProperty("-webkit-transition", "", "");
104 td.style.setProperty("transition", "", "");
105
106 window.setTimeout(() => {
107 td.style.setProperty("-webkit-transition", "background-color 1s", "");
108 td.style.setProperty("transition", "background-color 1s", "");
109 td.style.backgroundColor = "";
110 }, 1000);
111}
112
113function redirectEq(div, eqName) {
114 // update URL manually
115 let url = window.location.href;
116 if (url.search("&eq=") !== -1) {
117 url = url.slice(0, url.search("&eq="));
118 url += "&eq=" + eqName;
119 if (url !== window.location.href)
120 window.history.replaceState({}, "Equipment", url);
121 }
122
123 // delete lower part of old table
124 let table = document.getElementById(equipment.tid);
125 table.remove();
126
127 equipment = {
128 "name" : "",
129 "table" : [],
130 "vars": [],
131 "group": []
132 };
133
134 // add new equipment
135 eqtable_init(div, eqName, true, true);
136}
137
138let confirmFlag = 0;
139
140function validateData(value, elem) {
141 // avoid double call of validateData
142 // - once by direct call
143 // - once by ODBFinishInlineEdit if focus is lost
144 if (confirmFlag === 1)
145 return false;
146
147 // get selected rows
148 let nSel = 0;
149 equipment.table.forEach( e => {
150 if (e.selected)
151 nSel++;
152 });
153
154
155 if (nSel > 1) {
156 confirmFlag++;
157 dlgConfirm("You have selected " + nSel + " values. Do you want to change all of them to " + value + " ?",
158 setAll, { "value" : value, "elem": elem });
159 return false;
160 }
161 return true;
162}
163
164function setAll(flag, param) {
165 confirmFlag--;
166 if (flag === true) {
167 let tr = param.elem.parentNode.parentNode;
168 let index;
169 for (let i=0 ; i<tr.childNodes.length ; i++)
170 if (tr.childNodes[i] === param.elem.parentNode) {
171 index = i;
172 break;
173 }
174 equipment.table.forEach(e => {
175 if (index && e.selected) {
176 let m = e.tr.childNodes[index].firstChild;
177 m.setValue(param.value);
178 m.sendValueToOdb();
179 }
180 });
181 }
182}
183function goToOdb() {
184 window.location.href = "?cmd=odb&odb_path=/Equipment/" + equipment.name;
185}
186
187function selectGroup(t) {
188 for (let g of equipment.group) {
189 let trGroup = document.getElementsByName(g);
190
191 for (let tr of trGroup)
192 tr.style.display = (g === t.value || t.value === "All") ? "table-row" : "none";
193 }
194}
195
196function enable_row(row, enabled, index) {
197 let disabled = false;
198 if (enabled !== undefined) {
199 if (Array.isArray(enabled)) {
200 if (!enabled[index])
201 disabled = true;
202 } else if (enabled === false)
203 disabled = true;
204 }
205
206 row.disabled = disabled;
207
208 if (disabled)
209 row.style.backgroundColor = "#B0B0B0";
210 else
211 row.style.backgroundColor = "";
212
213 // go through row elements and disable input and select elements
214 for (let child of row.children) {
215 if (child.childNodes[0] && child.childNodes[0].dataset && child.childNodes[0].dataset.odbEditable !== undefined)
216 child.childNodes[0].dataset.odbEditable = disabled ? "0" : "1";
217 if (child.childNodes[0] && child.childNodes[0].tagName === "SELECT")
218 child.childNodes[0].disabled = disabled;
219 }
220}
221
222function pageSel(page) {
223
224 let ps = document.getElementsByClassName('pageSelector');
225 if (ps.length === 0)
226 return;
227
228 // change color of tabs
229 let pageIndex = 0;
230 for (let i= 0 ; i<ps.length ; i++) {
231 if (ps[i].children[0].innerText === page) {
232 pageIndex = i-1;
233 ps[i].style.backgroundColor = "#8cbdff";
234 ps[i].children[0].style.color = "#f8f8f8";
235 } else {
236 ps[i].style.backgroundColor = "#dddddd";
237 ps[i].children[0].style.color = "#0000ff";
238 }
239 }
240
241 // hide/unhide table rows
242 if (page === "All") {
243 for (let i=0 ; i<equipment.table.length ; i++)
244 equipment.table[i].tr.style.display = "";
245 } else {
246 for (let i=0 ; i<equipment.table.length ; i++) {
247 if (Math.floor(i / equipment.channelsPerDevice) === pageIndex)
248 equipment.table[i].tr.style.display = "";
249 else
250 equipment.table[i].tr.style.display = "none";
251 }
252 }
253
254 // modify extra values
255 let extra = document.getElementById('displayExtra');
256 if (extra) {
257 extra = extra.children[0].children[0].children[0];
258 for (let i=0 ; i<extra.children.length ; i++) {
259 let d = extra.children[i].children[0];
260 if (d.className === "modbselect" || d.className ==="modbvalue") {
261
262 let p = d.dataset.odbPath;
263
264 if (page === "All") {
265 if (p.includes('['))
266 p = p.replace(/\[.*?\]/g, "[*]");
267 else
268 p = p + "[*]";
269 } else {
270 if (p.includes('['))
271 p = p.replace(/\[.*?\]/g, "[" + pageIndex + "]");
272 else
273 p = p + "[" + pageIndex + "]";
274 }
275
276 d.dataset.odbPath = p;
277 }
278 }
279 }
280
281 equipment.selectedPage = pageIndex;
282
283 mhttpd_refresh();
284}
285
286function eqtable_init(div, eqName, bButtons, bEqSelect) {
287
288 if (eqName === undefined)
289 equipment.name = new URLSearchParams(window.location.search).get('eq');
290 else
291 equipment.name = eqName;
292
293 if (equipment.name === undefined || equipment.name === null) {
294 dlgAlert("Please specify equipment name in URL with \"eq=<name>\"");
295 return;
296 }
297
298 // add equipment table to <div>
299 if (div === undefined) {
300 dlgAlert("Please specify <div> element when calling eqtable_init()");
301 return;
302 }
303
304 equipment.tid = 'eqTable' + equipment.name.replace(/\s+/g, ''); // remove all spaces from eq
305
306 let html = '<table id="' + equipment.tid + '" class="mtable" style="visibility: hidden">';
307
308 if (bEqSelect)
309 html +=
310 '<tr>\n' +
311 ' <td id="tableHeader" class="mtableheader">\n' +
312 ' <select class="mthselect" id="eqSelect" onchange="redirectEq(document.getElementById(\'' + div.id + '\'), this.value)">\n' +
313 ' </select>' +
314 ' </td>' +
315 '</tr>';
316
317 html += '<tr><td id="fewarning" width="100%" style="text-align: center; display: none">' +
318 '<span style="font-size: 2em; padding: 5px; display: block; background-color: var(--mred);">' +
319 '<a href="?cmd=Programs">Frontend</a> not running</span>' +
320 '</td></tr>';
321
322 if (bButtons)
323 html += `
324 <tr>
325 <td id="menuButtons" style="border-radius: 12px;">
326 <button class="dlgButton" id="btnSave" title="Save current setting" onclick="saveFilePicker();">Save</button>
327 <button class="dlgButton" id="btnLoad" title="Load setting from file" style="display: none" onclick="loadFilePicker();">Load</button>
328 <button class="dlgButton" id="btnExport" title="Export current setting" onclick="exportFileQuery();">Export</button>
329 <button class="dlgButton" id="btnImport" title="Import current setting" style="display: none" onclick="importFile();">Import</button>
330 <button class="dlgButton" id="btnODB" title="Go to ODB" style="float: right" onclick="goToOdb();">ODB</button>
331 </td>
332 </tr>`;
333
334 html += '</table>';
335
336 div.innerHTML = html;
337
338 mjsonrpc_db_get_value('/Equipment/' + equipment.name).then(
339 rpc => {
340 let eq = rpc.result.data[0];
341 if (eq === null) {
342 dlgAlert('Equipment \"' + equipment.name + '\" does not exist in ODB',
343 () => window.location.href = ".");
344 return;
345 }
346
347 if (eq.variables === null) {
348 dlgAlert('No valid \"/Equipment/' + equipment.name + '/Variables\" in ODB',
349 () => window.location.href = ".");
350 return;
351 }
352
353 let table = document.getElementById(equipment.tid);
354 let columns = 0;
355 let nRows = 0;
356 let gridDisplay = true;
357
358 equipment.frontend_name = eq.common["frontend name"];
359
360 if (document.getElementById('eqSelect'))
361 document.getElementById('eqSelect').innerHTML = equipment.name;
362 if (document.getElementById('tableHeader'))
363 document.getElementById('tableHeader').style.width = "300px";
364
365 // obtain lengths of elements under /Variables
366 let nVar = 0;
367 for (const e in eq.variables) {
368 if (e.includes('/'))
369 continue;
370
371 nVar++;
372 let v = eq.variables[e];
373 let len = v.length ? v.length : 1;
374 if (nRows === 0)
375 nRows = len;
376 if (nRows !== len) {
377 gridDisplay = false;
378 break;
379 }
380 }
381
382 if (nVar < 2)
383 gridDisplay = false;
384
385 // overwrite gridDisplay flag from ODB if present
386 if (eq.settings && eq.settings["grid display"] === undefined) {
387 eq.settings["grid display"] = gridDisplay;
388 } else {
389 if (eq.settings)
390 gridDisplay = eq.settings["grid display"];
391 else
392 gridDisplay = false;
393 }
394
395 let cpd;
396 if (eq.settings)
397 cpd = eq.settings["channels per device"];
398 if (cpd && cpd > 0) {
399 // page selector
400 let tr = table.insertRow();
401 let td = tr.insertCell();
402 td.id = "pageSelector";
403 td.style.padding = "0";
404 td.style.borderBottom = "2px solid #808080";
405
406 let pageSel = "<table><tr>";
407 pageSel += "<td class=\"pageSelector\"><a href=\"#\" onclick=\"pageSel('All')\">All</a></td>";
408 eq.devNames = eq.settings["device names"];
409 if (eq.devNames !== undefined && !Array.isArray(eq.devNames))
410 eq.devNames = new Array(eq.devNames);
411
412 if (eq.devNames === undefined) {
413 eq.devNames = [];
414 for (let i = 0; i < nRows / cpd; i++)
415 eq.devNames[i] = String(i);
416 }
417
418 for (let i = 0; i < nRows / cpd; i++) {
419 pageSel += "<td class=\"pageSelector\"><a href=\"#\" onclick=\"pageSel('" + eq.devNames[i] +
420 "')\">" + eq.devNames[i] + "</a></td>";
421 }
422 pageSel += "</tr></table>";
423 td.innerHTML = pageSel;
424 equipment.channelsPerDevice = cpd;
425 }
426
427 // extra display at the top
428 if (eq.settings && eq.settings["display extra"]) {
429 let tr = table.insertRow();
430 let td = tr.insertCell();
431 td.id = "displayExtra";
432 td.style.padding = "0";
433
434 let extraTable = "<table><tr>";
435 let displayExtra = eq.settings["display extra"].split(',').map(item => item.trim());
436 for (const vName of displayExtra) {
437 td.style.height = "22px";
438
439 let subdir = "Variables";
440 if (eq.variables[vName.toLowerCase()] === undefined)
441 subdir = "Settings";
442 let editable = (eq.settings &&
443 eq.settings.editable && eq.settings.editable.includes(vName));
444
445 let bool = false;
446 if (eq.variables[vName.toLowerCase()] === undefined) {
447 if (Array.isArray(eq.settings[vName.toLowerCase()]))
448 bool = (typeof eq.settings[vName.toLowerCase()][0] === 'boolean');
449 else
450 bool = (typeof eq.settings[vName.toLowerCase()] === 'boolean');
451 } else {
452 if (Array.isArray(eq.variables[vName.toLowerCase()]))
453 bool = (typeof eq.variables[vName.toLowerCase()][0] === 'boolean');
454 else
455 bool = (typeof eq.variables[vName.toLowerCase()] === 'boolean');
456 }
457
458 let format;
459 let vDiv;
460 let unit;
461 if (eq.settings)
462 format = eq.settings['format ' + vName.toLowerCase()];
463 if (format === null)
464 format = undefined;
465 if (eq.settings)
466 unit = eq.settings['unit ' + vName.toLowerCase()];
467 if (unit === null)
468 unit = undefined;
469
470 if (editable) {
471 if (bool)
472 vDiv = "<select class=\"modbselect\" " +
473 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
474 "><option value='false'>No</option>" +
475 "<option value='true'>Yes</option>" +
476 "<option value='...'>...</option>" +
477 "onchange='valueChanged(this);' " +
478 "</select>";
479 else if (format)
480 vDiv = "<span class=\"modbvalue\" style=\"border-right: 1px black;\" " +
481 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
482 "data-odb-editable='1' " +
483 "data-size='10' " +
484 "data-format=\"" + format + "\" " +
485 "onchange='valueChanged(this);' " +
486 "></span>";
487 else
488 vDiv = "<span class=\"modbvalue\" " +
489 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
490 "data-odb-editable='1' " +
491 "data-size='10' " +
492 "onchange='valueChanged(this);' " +
493 "></span>";
494
495 } else {
496 if (format)
497 vDiv = "<span class=\"modbvalue\" " +
498 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
499 "data-format=\"" + format + "\" " +
500 "onchange='valueChanged(this);' " +
501 "></span>";
502 else
503 vDiv = "<span class=\"modbvalue\" " +
504 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
505 "onchange='valueChanged(this);' " +
506 "></span>";
507 }
508
509 if (unit)
510 vDiv += unit;
511
512 extraTable += "<td>";
513 extraTable += vName + ":&nbsp;" + vDiv + "&nbsp;";
514 extraTable += "</td>";
515 }
516
517 extraTable += "</tr></table>";
518 td.innerHTML = extraTable;
519 }
520
521 if (eq.settings && eq.settings.names !== undefined && !Array.isArray(eq.settings.names))
522 eq.settings.names = [eq.settings.names];
523
524 if (eq.settings)
525 equipment.enabled = eq.settings.enabled;
526 else
527 equipment.enabled = true;
528
529 let groupDisplay = false;
530 let groupList = [];
531 if (eq.settings && eq.settings.names !== undefined)
532 groupDisplay = eq.settings.names.every(e => e.includes('%'));
533
534 if (groupDisplay) {
535 let tr = table.insertRow();
536 let td = tr.insertCell();
537 td.id = "groupSelector";
538 let h = "Group:&nbsp;&nbsp;";
539
540 eq.settings.names.forEach(n => {
541 let g = n.substring(0, n.indexOf('%'));
542 if (!groupList.includes(g))
543 groupList.push(g);
544 });
545
546 h += "<select onchange='selectGroup(this)'>";
547 groupList.forEach(g => {
548 h += "<option value=\"" + g + "\">" + g + "</option>";
549 });
550 h += "<option value=\"All\">- All -</option>";
551 h += "</select>";
552 td.innerHTML = h;
553
554 equipment.group = groupList;
555 }
556
557 if (gridDisplay) {
558
559 // assemble display list
560 let display = [];
561
562 if (eq.settings && eq.settings.display) {
563 display = eq.settings.display.split(',').map(item => item.trim());
564 } else {
565 display.push("#");
566 display.push("Names");
567 let v = Object.entries(eq.variables)
568 .filter(([key, value]) => key.includes('/name'))
569 .map(([key, value]) => value);
570 display = display.concat(v);
571 }
572
573 let tr = table.insertRow();
574
575 for (const title of display) {
576 let th = document.createElement('th');
577 th.innerHTML = (title === "Names" ? "Name" : title);
578 th.className = "mtableheader mvarheader";
579
580 let subdir;
581 if (eq.variables[title.toLowerCase()] !== undefined)
582 subdir = "/Variables/";
583 else if (eq.settings && eq.settings[title.toLowerCase()] !== undefined)
584 subdir = "/Settings/";
585
586 if (eq.settings && eq.settings['unit ' + title.toLowerCase()]) {
587 th.colSpan = 2;
588 columns++;
589 }
590
591 tr.appendChild(th);
592 columns++;
593
594 let editable = (eq.settings &&
595 eq.settings.editable &&
596 eq.settings.editable.toLowerCase().includes(title.toLowerCase()));
597
598 // enable load/import if we have editable variables
599 if (editable && bButtons) {
600 document.getElementById('btnLoad').style.display = "inline";
601 document.getElementById('btnImport').style.display = "inline";
602 }
603
604 equipment.vars.push({
605 'name': title,
606 'editable': editable,
607 'subdir': subdir
608 });
609 }
610
611 for (let i = 0; i < nRows; i++) {
612 if (equipment.table.length <= i)
613 equipment.table.push({});
614
615 equipment.table[i].selected = false;
616 equipment.table[i].lastSelected = false;
617
618 // create new row, install listener and set style
619 let tr = table.insertRow();
620
621 tr.id = "spTr";
622 if (cpd && cpd > 0)
623 tr.classList.add("p" + Math.floor(i / cpd));
624
625 tr.addEventListener('mousedown', mouseEvent);
626 tr.addEventListener('mousemove', mouseEvent);
627 tr.addEventListener('mouseout', mouseEvent);
628
629 // catch all mouseup events to stop dragging
630 document.addEventListener('mouseup', mouseEvent);
631
632 tr.style.userSelect = 'none';
633
634 if (groupDisplay) {
635 let group = eq.settings.names[i];
636 group = group.substring(0, group.indexOf('%'));
637 tr.setAttribute('name', group);
638 if (groupList.length > 0 && group !== groupList[0])
639 tr.style.display = "none";
640 }
641
642 equipment.table[i].tr = tr;
643
644 // iterate over all variables in equipment
645 for (let v of equipment.vars) {
646
647 let td = tr.insertCell();
648 td.style.textAlign = "right";
649
650 if (eq.settings && eq.settings.group)
651 if (eq.settings.group[i])
652 td.style.borderTop = "2px solid #808080";
653
654 if (v.name === "#") {
655 td.innerHTML = i.toString();
656 td.style.width = "10px";
657 } else if (v.name === "Names") {
658 td.style.textAlign = "center";
659 if (eq.settings && eq.settings.names) {
660 if (groupDisplay)
661 td.innerHTML = eq.settings.names[i].substring(eq.settings.names[i].indexOf('%') + 1);
662 else
663 td.innerHTML = eq.settings.names[i];
664 }
665 } else {
666 let format = eq.settings && eq.settings['format ' + v.name.toLowerCase()];
667 if (format === undefined || format === null)
668 format = "%f2"; // default format
669 else if (Array.isArray(format))
670 format = format[i];
671
672 if (v.editable) {
673
674 let b = false;
675 if (v.subdir.includes("Variables")) {
676 b = typeof eq.variables[v.name.toLowerCase()] === 'boolean' ||
677 (typeof eq.variables[v.name.toLowerCase()] === 'object' &&
678 typeof eq.variables[v.name.toLowerCase()][0] === 'boolean');
679 } else if (v.subdir.includes("Settings")) {
680 b = typeof eq.settings[v.name.toLowerCase()] === 'boolean' ||
681 (typeof eq.settings[v.name.toLowerCase()] === 'object' &&
682 typeof eq.settings[v.name.toLowerCase()][0] === 'boolean');
683 }
684
685 if (b) {
686 td.innerHTML = "<select class=\"modbselect\" " +
687 "data-odb-path=\"/Equipment/" + equipment.name + v.subdir + v.name + "[" + i + "]\" " +
688 "data-validate='validateData' " +
689 "><option value='false'>No</option>" +
690 "<option value='true'>Yes</option>" +
691 "</select>";
692 } else {
693 td.style.width = "90px";
694 td.style.height = "22px";
695 td.innerHTML = "<div class=\"modbvalue\" " +
696 "data-odb-path=\"/Equipment/" + equipment.name + v.subdir + v.name + "[" + i + "]\" " +
697 "data-odb-editable='1' " +
698 "data-size='10' " +
699 "data-format=\"" + format + "\" " +
700 "data-validate='validateData' " +
701 "onchange='valueChanged(this);' " +
702 "></div>";
703 }
704 } else
705 td.innerHTML = "<div class=\"modbvalue\" " +
706 "data-odb-path=\"/Equipment/" + equipment.name + v.subdir + v.name + "[" + i + "]\" " +
707 "data-format=\"" + format + "\" " +
708 "onchange='valueChanged(this);' " +
709 "></div>";
710
711 // Unit
712 if (eq.settings && eq.settings['unit ' + v.name.toLowerCase()]) {
713
714 let unit = eq.settings['unit ' + v.name.toLowerCase()];
715 if (Array.isArray(unit))
716 unit = unit[i];
717
718 td = tr.insertCell();
719 td.style.width = "10px";
720
721 if (eq.settings && eq.settings.group)
722 if (eq.settings.group[i])
723 td.style.borderTop = "2px solid #808080";
724
725 td.innerHTML = unit;
726 } else
727 td.style.textAlign = 'center';
728
729 }
730 }
731
732 document.getElementById(equipment.tid).style.visibility = '';
733
734 if (cpd && cpd > 0) {
735 enable_row(tr, eq.settings.enabled, Math.floor(i / cpd));
736 } else {
737 if (eq.settings === undefined)
738 enable_row(tr, true, i);
739 else {
740 if (Array.isArray(eq.settings.enabled))
741 enable_row(tr, eq.settings.enabled[i], i);
742 else
743 enable_row(tr, eq.settings.enabled, i);
744 }
745 }
746 }
747
748 if (cpd && cpd > 0)
749 pageSel(eq.devNames[0]);
750
751 } else { // ------------ non-grid display
752
753 let n = 0;
754
755 for (const e in eq.variables) {
756 if (!e.includes('/name'))
757 continue;
758
759 let vName = eq.variables[e];
760 let vArr = eq.variables[vName.toLowerCase()];
761 let editable;
762 if (eq.settings && eq.settings.editable) {
763 if (Array.isArray(eq.settings.editable))
764 editable = eq.settings.editable;
765 else
766 editable = eq.settings.editable.toLowerCase().includes(vName.toLowerCase());
767 }
768
769 // skip subdirectories
770 if (typeof vArr === 'object' && !Array.isArray(vArr))
771 continue;
772
773 let flag = editable;
774 if (Array.isArray(editable))
775 flag = editable.includes(true);
776
777 equipment.vars.push({
778 'name': vName,
779 'editable': flag
780 });
781
782 // enable load/import if we have editable variables
783 if (flag && bButtons) {
784 document.getElementById('btnLoad').style.display = "inline";
785 document.getElementById('btnImport').style.display = "inline";
786 }
787
788 let tr = table.insertRow();
789
790 // Index
791 let th = document.createElement('th');
792 th.className = "mtableheader mvarheader";
793 th.innerHTML = "#";
794 tr.appendChild(th);
795 columns = 1;
796
797 // Name
798 if (eq.settings && eq.settings['names ' + vName.toLowerCase()] !== undefined) {
799 let th = document.createElement('th');
800 th.className = "mtableheader mvarheader";
801 th.innerHTML = "Name";
802 tr.appendChild(th);
803 columns = 2;
804 }
805
806 th = document.createElement('th');
807 th.innerHTML = vName;
808 th.className = "mtableheader mvarheader";
809 th.colSpan = 2;
810 tr.appendChild(th);
811 columns += 2;
812
813 // variables
814 if (!Array.isArray(vArr))
815 vArr = [vArr];
816 for (let i = 0; i < vArr.length; i++) {
817
818 equipment.table.push({});
819
820 equipment.table[n].selected = false;
821 equipment.table[n].lastSelected = false;
822
823 // create new row, install listener and set style
824 let tr = table.insertRow();
825
826 tr.id = "spTr";
827 tr.addEventListener('mousedown', mouseEvent);
828 tr.addEventListener('mousemove', mouseEvent);
829 tr.addEventListener('mouseout', mouseEvent);
830
831 // catch all mouseup events to stop dragging
832 document.addEventListener('mouseup', mouseEvent);
833
834 tr.style.userSelect = 'none';
835
836 if (groupDisplay) {
837 let group = eq.settings.names[i];
838 group = group.substring(0, group.indexOf('%'));
839 tr.setAttribute('name', group);
840 if (groupList.length > 0 && group !== groupList[0])
841 tr.style.display = "none";
842 }
843
844 equipment.table[n].tr = tr;
845
846 // index and name
847 if (eq.settings && eq.settings['names ' + vName.toLowerCase()] !== undefined) {
848 td = tr.insertCell();
849 if (eq.settings && eq.settings.group)
850 if (eq.settings.group[i])
851 td.style.borderTop = "2px solid #808080";
852
853 td.innerHTML = i;
854
855 td = tr.insertCell();
856 if (eq.settings && eq.settings.group)
857 if (eq.settings.group[i])
858 td.style.borderTop = "2px solid #808080";
859
860 if (Array.isArray(eq.settings['names ' + vName.toLowerCase()])) {
861 td.innerHTML = eq.settings['names ' + vName.toLowerCase()][i]
862 } else {
863 td.innerHTML = eq.settings['names ' + vName.toLowerCase()];
864 }
865 } else {
866 td = tr.insertCell();
867
868 if (eq.settings && eq.settings.group)
869 if (eq.settings.group[i])
870 td.style.borderTop = "2px solid #808080";
871
872 td.innerHTML = vName + '[' + i + ']';
873 }
874
875 // units
876 let unit;
877 if (eq.settings && Array.isArray(eq.settings['unit ' + vName.toLowerCase()]))
878 unit = eq.settings['unit ' + vName.toLowerCase()];
879 else if (eq.settings && eq.settings['unit ' + vName.toLowerCase()])
880 unit = new Array(vArr.length).fill(eq.settings['unit ' + vName.toLowerCase()]);
881
882 // value
883 td = tr.insertCell();
884
885 if (eq.settings && eq.settings.group)
886 if (eq.settings.group[i])
887 td.style.borderTop = "2px solid #808080";
888
889 let format;
890 if (eq.settings)
891 format = eq.settings['format ' + vName.toLowerCase()];
892 if (format === undefined || format === null)
893 format = new Array(vArr.length).fill("%f2"); // default format
894
895 let flag = editable;
896 if (Array.isArray(flag))
897 flag = flag[n];
898
899 if (flag) {
900 td.style.width = "90px";
901 td.style.height = "22px";
902 td.style.textAlign = "right";
903 td.innerHTML = "<div class=\"modbvalue\" " +
904 "data-odb-path=\"/Equipment/" + equipment.name + "/Variables/" + vName + "[" + i + "]\" " +
905 "data-odb-editable='1' " +
906 "data-size='10' " +
907 "data-format=\"" + format[i] + "\" " +
908 "onchange='valueChanged(this);' " +
909 "></div>";
910 } else {
911 td.style.textAlign = "right";
912 td.innerHTML = "<div class=\"modbvalue\" " +
913 "data-odb-path=\"/Equipment/" + equipment.name + "/Variables/" + vName + "[" + i + "]\" " +
914 "data-format=\"" + format[i] + "\" " +
915 "onchange='valueChanged(this);' " +
916 "></div>";
917 }
918
919 td = tr.insertCell();
920 td.style.width = "10px";
921
922 if (eq.settings && eq.settings.group)
923 if (eq.settings.group[i])
924 td.style.borderTop = "2px solid #808080";
925
926 if (unit)
927 td.innerHTML = unit[i];
928 else
929 td.innerHTML = " ";
930
931 n++;
932 }
933 }
934
935 document.getElementById(equipment.tid).style.visibility = '';
936 }
937
938 if (document.getElementById('fewarning'))
939 document.getElementById('fewarning').colSpan = columns;
940 if (document.getElementById('tableHeader'))
941 document.getElementById('tableHeader').colSpan = columns;
942 if (document.getElementById('menuButtons'))
943 document.getElementById('menuButtons').colSpan = columns;
944 if (groupDisplay)
945 document.getElementById('groupSelector').colSpan = columns;
946 if (document.getElementById('displayExtra'))
947 document.getElementById('displayExtra').colSpan = columns;
948 if (document.getElementById('pageSelector'))
949 document.getElementById('pageSelector').colSpan = columns;
950
951 // populate equipment table
952 if (bEqSelect) {
953 mjsonrpc_db_ls(["/Equipment"]).then(
954 rpc => {
955 let eqList = rpc.result.data[0];
956 let sel = document.getElementById('eqSelect');
957 for (eq in eqList) {
958 let o = document.createElement('option');
959 o.text = eq;
960 sel.add(o);
961 }
962 sel.value = equipment.name;
963 });
964 }
965
966 let d = document.getElementById(equipment.tid);
967 if (d) {
968 d = d.parentNode.parentNode.parentNode;
969 if (d.tagName === 'DIV')
970 dlgCenter(d);
971 }
972
973 check_frontend();
974
975 document.addEventListener('mousedown', removeSelection);
976 mhttpd_refresh();
977 });
978}
979
980let allowIncDec = false;
981
982function saveFilePicker() {
983 file_picker("equipment", "*.json", saveFile, true, equipment.name, true);
984}
985
986function saveFile(filename) {
987 mjsonrpc_db_copy(['/Equipment/' + equipment.name + '/Variables']).then(rpc => {
988
989 if (filename.indexOf('.json') === -1)
990 filename += '.json';
991
992 let header = {
993 "/MIDAS version": "2.1",
994 "/filename": filename,
995 "/ODB path": "/Equipment/" + equipment.name + "/Variables"
996 }
997 header = JSON.stringify(header, null, ' ');
998 header = header.substring(0, header.length-2) + ','; // strip trailing '}'
999
1000 let odbJson = JSON.stringify(rpc.result.data[0], null, ' ');
1001 if (odbJson.indexOf('{') === 0)
1002 odbJson = odbJson.substring(1); // strip leading '{'
1003
1004 odbJson = header + odbJson;
1005
1006 file_save_ascii(filename, odbJson, "Equipment \"" + equipment.name +
1007 "\" saved to file \"" + filename + "\"");
1008
1009 }).catch(error => mjsonrpc_error_alert(error));
1010}
1011
1012function exportFileQuery() {
1013 dlgQuery("Enter filename: ", "default.json", exportFile);
1014}
1015
1016function exportFile(filename) {
1017 if (filename === false)
1018 return;
1019 mjsonrpc_db_copy(['/Equipment/' + equipment.name + '/Variables']).then(rpc => {
1020
1021 if (filename.indexOf('.json') === -1)
1022 filename += '.json';
1023
1024 let header = {
1025 "/MIDAS version": "2.1",
1026 "/filename": filename,
1027 "/ODB path": "/Equipment/" + equipment.name + "/Variables"
1028 }
1029 header = JSON.stringify(header, null, ' ');
1030 header = header.substring(0, header.length-2) + ','; // strip trailing '}'
1031
1032 let odbJson = JSON.stringify(rpc.result.data[0], null, ' ');
1033 if (odbJson.indexOf('{') === 0)
1034 odbJson = odbJson.substring(1); // strip leading '{'
1035
1036 odbJson = header + odbJson;
1037
1038 // use trick from FileSaver.js
1039 let a = document.getElementById('downloadHook');
1040 if (a === null) {
1041 a = document.createElement("a");
1042 a.style.display = "none";
1043 a.id = "downloadHook";
1044 document.body.appendChild(a);
1045 }
1046
1047 let blob = new Blob([odbJson], {type: "text/json"});
1048 let url = window.URL.createObjectURL(blob);
1049
1050 a.href = url;
1051 a.download = filename;
1052 a.click();
1053 window.URL.revokeObjectURL(url);
1054 dlgAlert("Equipment \"" + equipment.name +
1055 "\" downloaded to file \"" + filename + "\"");
1056
1057 }).catch(error => mjsonrpc_error_alert(error));
1058}
1059
1060function loadFilePicker() {
1061 file_picker("equipment", "*.json", loadFile);
1062}
1063
1064function loadFile(filename) {
1065 file_load_ascii(filename, (text) => fileInterprete(filename, text) );
1066}
1067
1068async function importFile() {
1069 // Chrome has file picker, others might have not
1070 try {
1071 let fileHandle;
1072 [fileHandle] = await window.showOpenFilePicker();
1073 const file = await fileHandle.getFile();
1074 const text = await file.text();
1075 fileInterprete(file.name, text);
1076 } catch (error) {
1077 if (error.name !== 'AbortError') {
1078 // fall-back to old method
1079 if (document.getElementById('dlgFileSelect') === null) {
1080 let fsDiv = document.createElement('div');
1081 fsDiv.id = "dlgFileSelect";
1082 fsDiv.className = "dlgFrame";
1083 fsDiv.innerHTML = dlgFileSelect;
1084 document.body.appendChild(fsDiv);
1085 }
1086 dlgShow('dlgFileSelect', true);
1087 }
1088 }
1089}
1090
1091function importFileFromSelector() {
1092
1093 let input = document.getElementById('fileSelector');
1094 let file = input.files[0];
1095 if (file !== undefined) {
1096 let reader = new FileReader();
1097 reader.readAsText(file);
1098
1099 reader.onerror = function () {
1100 dlgAlert('File read error: ' + reader.error);
1101 }
1102
1103 reader.onload = function () {
1104 fileInterprete(file.name, reader.result);
1105 }
1106 }
1107}
1108
1109function fileInterprete(filename, text) {
1110 try {
1111 let j = JSON.parse(text);
1112
1113 // check for correct equipment
1114 let path = j["/ODB path"].split('/');
1115 if (path[2] !== equipment.name) {
1116 dlgAlert("File contains settings for equipment \"" + path[2] + "\"<br />"+
1117 "and therefore cannot be loaded into equipment \"" + equipment.name +"\"");
1118 return;
1119 }
1120
1121 mhttpd_refresh_pause(true);
1122
1123 for (let v of equipment.vars)
1124 if (v.editable)
1125 modbset("/Equipment/" + equipment.name + "/Variables/" + v.name, j[v.name]);
1126
1127 mhttpd_refresh_pause(false);
1128
1129 window.setTimeout(mhttpd_refresh, 10);
1130
1131 mjsonrpc_cm_msg("Values loaded from file \"" + filename + "\"");
1132 } catch (error) {
1133 dlgAlert("File \"" + filename + "\" is not a valid JSON file:<br /><br />" + error);
1134 }
1135}
1136
1137function renderSelection() {
1138 // change color according to selection
1139 for (let i=0 ; i<equipment.table.length ; i++) {
1140 if (equipment.table[i].selected) {
1141 equipment.table[i].tr.style.backgroundColor = '#004CBD';
1142 equipment.table[i].tr.style.color = '#FFFFFF';
1143
1144 // change <a> color
1145 if (equipment.table[i].tr.getElementsByTagName('a').length > 0)
1146 equipment.table[i].tr.getElementsByTagName('a')[0].style.color = '#FFFFFF';
1147 } else {
1148
1149 let index = i;
1150
1151 if (equipment.channelsPerDevice)
1152 index = Math.floor(i / equipment.channelsPerDevice);
1153 enable_row(equipment.table[i].tr, equipment.enabled, index);
1154
1155 equipment.table[i].tr.style.color = ''; // text is black again
1156
1157 // change <a> color
1158 let a = equipment.table[i].tr.getElementsByTagName('a');
1159 if (a.length > 0) {
1160 a = a[0];
1161 a.style.color = '';
1162 }
1163 }
1164 }
1165}
1166
1167let mouseDragged = false;
1168
1169// function gets called from document
1170function removeSelection(e) {
1171 // don't de-select if we click on "->" or on drop-down box
1172 if (e.target.tagName === 'BUTTON' || e.target.tagName === 'SELECT')
1173 return;
1174
1175 for (let i = 0; i < equipment.table.length; i++) {
1176 equipment.table[i].selected = false;
1177 equipment.table[i].lastSelected = false;
1178 }
1179 renderSelection();
1180}
1181
1182function getSelectedElements() {
1183 let sel = [];
1184 for (let i = 0; i < equipment.table.length; i++)
1185 if (equipment.table[i].selected)
1186 sel.push(i);
1187
1188 return sel;
1189}
1190
1191function mouseEvent(e) {
1192
1193 if (e.type === 'mouseup')
1194 mouseDragged = false;
1195
1196 // keep off secondary mouse buttons
1197 if (e.button !== 0)
1198 return;
1199
1200 // keep off drop down boxes
1201 if (e.target.tagName === 'SELECT')
1202 return;
1203
1204 e.preventDefault();
1205 e.stopPropagation();
1206
1207 // find current row
1208 let index;
1209 for (index = 0; index < equipment.table.length; index++)
1210 if (equipment.table[index].tr === e.target ||
1211 equipment.table[index].tr.contains(e.target))
1212 break;
1213 if (index === equipment.table.length)
1214 index = undefined;
1215
1216 // ignore clicks to <button>'a and <a>'s
1217 if (e.target.tagName === 'A' || e.target.tagName === 'BUTTON' || e.target.tagName === 'IMG')
1218 return;
1219
1220 if (e.type === 'mousedown') {
1221 mouseDragged = true;
1222
1223 if (e.shiftKey) {
1224
1225 // search last selected element
1226 let i1;
1227 for (i1 = 0 ; i1 < equipment.table.length ; i1++)
1228 if (equipment.table[i1].lastSelected)
1229 break;
1230 if (i1 === equipment.table.length)
1231 i1 = 0; // none selected, so use first one
1232
1233 if (i1 > index)
1234 [i1, index] = [index, i1];
1235
1236 for (let i=i1 ; i<= index ; i++)
1237 if (!equipment.table[i].tr.disabled)
1238 equipment.table[i].selected = true;
1239
1240 } else if (e.metaKey || e.ctrlKey) {
1241
1242 console.log("index = " + index);
1243
1244 // just toggle current selection
1245 if (!equipment.table[index].tr.disabled)
1246 equipment.table[index].selected = !equipment.table[index].selected;
1247
1248 // remember which row was last selected
1249 for (let i=0 ; i<equipment.table.length ; i++)
1250 equipment.table[i].lastSelected = false;
1251 if (equipment.table[index].selected)
1252 equipment.table[index].lastSelected = true;
1253
1254 } else {
1255 // no key pressed -> un-select all but current
1256 for (let i = 0; i < equipment.table.length; i++) {
1257 equipment.table[i].selected = false;
1258 equipment.table[i].lastSelected = false;
1259 }
1260
1261 if (!equipment.table[index].tr.disabled) {
1262 equipment.table[index].selected = true;
1263 equipment.table[index].lastSelected = true;
1264 }
1265 }
1266
1267 renderSelection();
1268 }
1269
1270 if (e.type === 'mousemove' && mouseDragged) {
1271
1272 // don't do dragging if shift or ctrl key pressed
1273 if (!e.shiftKey && !e.metaKey && !e.ctrlKey) {
1274 // unselect all
1275 for (let i = 0; i < equipment.table.length; i++)
1276 equipment.table[i].selected = false;
1277
1278 // current row
1279 let i2;
1280 for (i2 = 0; i2 < equipment.table.length; i2++)
1281 if (equipment.table[i2].tr === e.target ||
1282 equipment.table[i2].tr.contains(e.target))
1283 break;
1284
1285 if (i2 < equipment.table.length) {
1286 // search last selected element
1287 let i1;
1288 for (i1 = 0; i1 < equipment.table.length; i1++)
1289 if (equipment.table[i1].lastSelected)
1290 break;
1291 if (i1 === equipment.table.length)
1292 i1 = 0; // none selected, so use first one
1293
1294 if (i1 > i2)
1295 [i1, i2] = [i2, i1];
1296
1297 for (let i = i1; i <= i2; i++)
1298 if (!equipment.table[i].tr.disabled)
1299 equipment.table[i].selected = true;
1300
1301 renderSelection();
1302 }
1303 }
1304 }
1305}
1306
1307function dlgEquipment(eqName, bButtons = false, bEqSelect = false) {
1308
1309 // check if equipment exists in ODB
1310 mjsonrpc_db_ls(['/Equipment/' + eqName]).then(
1311 rpc => {
1312 let eq = rpc.result.data[0];
1313 if (eq === null) {
1314 dlgAlert('dlgEquipment: Equipment \"' + eqName + '\" does not exist in ODB');
1315 return;
1316 }
1317
1318 if (eq.variables === null) {
1319 dlgAlert('dlgEquipment: No valid \"/Equipment/' + eqName + '/Variables\" in ODB');
1320 return;
1321 }
1322
1323 let d = document.getElementById('dlgEqTitle');
1324
1325 // If dialog is already there, reuse it.
1326 if (!d) {
1327 d = document.createElement("div");
1328 d.className = "dlgFrame";
1329 d.style.zIndex = "30";
1330 d.shouldDestroy = true;
1331
1332 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgEqTitle\">" + eqName + "</div>" +
1333 "<div class=\"dlgPanel\" style=\"padding: 4px;\">" + "<div id=\"dlgEquipment\">" + "</div></div>";
1334 document.body.appendChild(d);
1335 dlgShow(d);
1336 }
1337
1338 eqtable_init(document.getElementById('dlgEquipment'), eqName, bButtons, bEqSelect);
1339
1340 if (bEqSelect) {
1341 let eqSelect = document.getElementById('eqSelect');
1342 eqSelect.onchange = (event) => {
1343 redirectEq(document.getElementById('dlgEquipment'), event.target.value);
1344 };
1345 }
1346 });
1347
1348}
1349
1350