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 hist(title, str) {
287 mhistory_dialog(title, str);
288}
289
290function eqtable_init(div, eqName, bButtons, bEqSelect) {
291
292 if (eqName === undefined)
293 equipment.name = new URLSearchParams(window.location.search).get('eq');
294 else
295 equipment.name = eqName;
296
297 if (equipment.name === undefined || equipment.name === null) {
298 dlgAlert("Please specify equipment name in URL with \"eq=<name>\"");
299 return;
300 }
301
302 // add equipment table to <div>
303 if (div === undefined) {
304 dlgAlert("Please specify <div> element when calling eqtable_init()");
305 return;
306 }
307
308 equipment.tid = 'eqTable' + equipment.name.replace(/\s+/g, ''); // remove all spaces from eq
309
310 let html = '<table id="' + equipment.tid + '" class="mtable" style="visibility: hidden">';
311
312 if (bEqSelect)
313 html +=
314 '<tr>\n' +
315 ' <td id="tableHeader" class="mtableheader">\n' +
316 ' <select class="mthselect" id="eqSelect" onchange="redirectEq(document.getElementById(\'' + div.id + '\'), this.value)">\n' +
317 ' </select>' +
318 ' </td>' +
319 '</tr>';
320
321 html += '<tr><td id="fewarning" width="100%" style="text-align: center; display: none">' +
322 '<span style="font-size: 2em; padding: 5px; display: block; background-color: var(--mred);">' +
323 '<a href="?cmd=Programs">Frontend</a> not running</span>' +
324 '</td></tr>';
325
326 if (bButtons)
327 html += `
328 <tr>
329 <td id="menuButtons" style="border-radius: 12px;">
330 <button class="dlgButton" id="btnSave" title="Save current setting" onclick="saveFilePicker();">Save</button>
331 <button class="dlgButton" id="btnLoad" title="Load setting from file" style="display: none" onclick="loadFilePicker();">Load</button>
332 <button class="dlgButton" id="btnExport" title="Export current setting" onclick="exportFileQuery();">Export</button>
333 <button class="dlgButton" id="btnImport" title="Import current setting" style="display: none" onclick="importFile();">Import</button>
334 <button class="dlgButton" id="btnODB" title="Go to ODB" style="float: right" onclick="goToOdb();">ODB</button>
335 </td>
336 </tr>`;
337
338 html += '</table>';
339
340 div.innerHTML = html;
341
342 mjsonrpc_db_get_value('/Equipment/' + equipment.name).then(
343 rpc => {
344 let eq = rpc.result.data[0];
345 if (eq === null) {
346 dlgAlert('Equipment \"' + equipment.name + '\" does not exist in ODB',
347 () => window.location.href = ".");
348 return;
349 }
350
351 if (eq.variables === null) {
352 dlgAlert('No valid \"/Equipment/' + equipment.name + '/Variables\" in ODB',
353 () => window.location.href = ".");
354 return;
355 }
356
357 let table = document.getElementById(equipment.tid);
358 let columns = 0;
359 let nRows = 0;
360 let gridDisplay = true;
361
362 equipment.frontend_name = eq.common["frontend name"];
363
364 if (document.getElementById('eqSelect'))
365 document.getElementById('eqSelect').innerHTML = equipment.name;
366 if (document.getElementById('tableHeader'))
367 document.getElementById('tableHeader').style.width = "300px";
368
369 // obtain lengths of elements under /Variables
370 let nVar = 0;
371 for (const e in eq.variables) {
372 if (e.includes('/'))
373 continue;
374
375 nVar++;
376 let v = eq.variables[e];
377 let len = v.length ? v.length : 1;
378 if (nRows === 0)
379 nRows = len;
380 if (nRows !== len) {
381 gridDisplay = false;
382 break;
383 }
384 }
385
386 if (nVar < 2)
387 gridDisplay = false;
388
389 // overwrite gridDisplay flag from ODB if present
390 if (eq.settings && eq.settings["grid display"] === undefined) {
391 eq.settings["grid display"] = gridDisplay;
392 } else {
393 if (eq.settings)
394 gridDisplay = eq.settings["grid display"];
395 else
396 gridDisplay = false;
397 }
398
399 let cpd;
400 if (eq.settings)
401 cpd = eq.settings["channels per device"];
402 if (cpd && cpd > 0) {
403 // page selector
404 let tr = table.insertRow();
405 let td = tr.insertCell();
406 td.id = "pageSelector";
407 td.style.padding = "0";
408 td.style.borderBottom = "2px solid #808080";
409
410 let pageSel = "<table><tr>";
411 pageSel += "<td class=\"pageSelector\"><a href=\"#\" onclick=\"pageSel('All')\">All</a></td>";
412 eq.devNames = eq.settings["device names"];
413 if (eq.devNames !== undefined && !Array.isArray(eq.devNames))
414 eq.devNames = new Array(eq.devNames);
415
416 if (eq.devNames === undefined) {
417 eq.devNames = [];
418 for (let i = 0; i < nRows / cpd; i++)
419 eq.devNames[i] = String(i);
420 }
421
422 for (let i = 0; i < nRows / cpd; i++) {
423 pageSel += "<td class=\"pageSelector\"><a href=\"#\" onclick=\"pageSel('" + eq.devNames[i] +
424 "')\">" + eq.devNames[i] + "</a></td>";
425 }
426 pageSel += "</tr></table>";
427 td.innerHTML = pageSel;
428 equipment.channelsPerDevice = cpd;
429 }
430
431 // extra display at the top
432 if (eq.settings && eq.settings["display extra"]) {
433 let tr = table.insertRow();
434 let td = tr.insertCell();
435 td.id = "displayExtra";
436 td.style.padding = "0";
437
438 let extraTable = "<table><tr>";
439 let displayExtra = eq.settings["display extra"].split(',').map(item => item.trim());
440 for (const vName of displayExtra) {
441 td.style.height = "22px";
442
443 let subdir = "Variables";
444 if (eq.variables[vName.toLowerCase()] === undefined)
445 subdir = "Settings";
446 let editable = (eq.settings &&
447 eq.settings.editable && eq.settings.editable.includes(vName));
448
449 let bool = false;
450 if (eq.variables[vName.toLowerCase()] === undefined) {
451 if (Array.isArray(eq.settings[vName.toLowerCase()]))
452 bool = (typeof eq.settings[vName.toLowerCase()][0] === 'boolean');
453 else
454 bool = (typeof eq.settings[vName.toLowerCase()] === 'boolean');
455 } else {
456 if (Array.isArray(eq.variables[vName.toLowerCase()]))
457 bool = (typeof eq.variables[vName.toLowerCase()][0] === 'boolean');
458 else
459 bool = (typeof eq.variables[vName.toLowerCase()] === 'boolean');
460 }
461
462 let format;
463 let vDiv;
464 let unit;
465 if (eq.settings)
466 format = eq.settings['format ' + vName.toLowerCase()];
467 if (format === null)
468 format = undefined;
469 if (eq.settings)
470 unit = eq.settings['unit ' + vName.toLowerCase()];
471 if (unit === null)
472 unit = undefined;
473
474 if (editable) {
475 if (bool)
476 vDiv = "<select class=\"modbselect\" " +
477 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
478 "><option value='false'>No</option>" +
479 "<option value='true'>Yes</option>" +
480 "<option value='...'>...</option>" +
481 "onchange='valueChanged(this);' " +
482 "</select>";
483 else if (format)
484 vDiv = "<span class=\"modbvalue\" style=\"border-right: 1px black;\" " +
485 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
486 "data-odb-editable='1' " +
487 "data-size='10' " +
488 "data-format=\"" + format + "\" " +
489 "onchange='valueChanged(this);' " +
490 "></span>";
491 else
492 vDiv = "<span class=\"modbvalue\" " +
493 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
494 "data-odb-editable='1' " +
495 "data-size='10' " +
496 "onchange='valueChanged(this);' " +
497 "></span>";
498
499 } else {
500 if (format)
501 vDiv = "<span class=\"modbvalue\" " +
502 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
503 "data-format=\"" + format + "\" " +
504 "onchange='valueChanged(this);' " +
505 "></span>";
506 else
507 vDiv = "<span class=\"modbvalue\" " +
508 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
509 "onchange='valueChanged(this);' " +
510 "></span>";
511 }
512
513 if (unit)
514 vDiv += unit;
515
516 extraTable += "<td>";
517 extraTable += vName + ":&nbsp;" + vDiv + "&nbsp;";
518 extraTable += "</td>";
519 }
520
521 extraTable += "</tr></table>";
522 td.innerHTML = extraTable;
523 }
524
525 if (eq.settings && eq.settings.names !== undefined && !Array.isArray(eq.settings.names))
526 eq.settings.names = [eq.settings.names];
527
528 if (eq.settings)
529 equipment.enabled = eq.settings.enabled;
530 else
531 equipment.enabled = true;
532
533 let groupDisplay = false;
534 let groupList = [];
535 if (eq.settings && eq.settings.names !== undefined)
536 groupDisplay = eq.settings.names.every(e => e.includes('%'));
537
538 if (groupDisplay) {
539 let tr = table.insertRow();
540 let td = tr.insertCell();
541 td.id = "groupSelector";
542 let h = "Group:&nbsp;&nbsp;";
543
544 eq.settings.names.forEach(n => {
545 let g = n.substring(0, n.indexOf('%'));
546 if (!groupList.includes(g))
547 groupList.push(g);
548 });
549
550 h += "<select onchange='selectGroup(this)'>";
551 groupList.forEach(g => {
552 h += "<option value=\"" + g + "\">" + g + "</option>";
553 });
554 h += "<option value=\"All\">- All -</option>";
555 h += "</select>";
556 td.innerHTML = h;
557
558 equipment.group = groupList;
559 }
560
561 if (gridDisplay) {
562
563 // assemble display list
564 let display = [];
565
566 if (eq.settings && eq.settings.display) {
567 display = eq.settings.display.split(',').map(item => item.trim());
568 } else {
569 display.push("#");
570 display.push("Names");
571 let v = Object.entries(eq.variables)
572 .filter(([key, value]) => key.includes('/name'))
573 .map(([key, value]) => value);
574 display = display.concat(v);
575 }
576
577 let tr = table.insertRow();
578
579 for (const title of display) {
580 let th = document.createElement('th');
581 th.innerHTML = (title === "Names" ? "Name" : title);
582 th.className = "mtableheader mvarheader";
583
584 let subdir;
585 if (eq.variables[title.toLowerCase()] !== undefined)
586 subdir = "/Variables/";
587 else if (eq.settings && eq.settings[title.toLowerCase()] !== undefined)
588 subdir = "/Settings/";
589
590 if (eq.settings && eq.settings['unit ' + title.toLowerCase()]) {
591 th.colSpan = 2;
592 columns++;
593 }
594
595 tr.appendChild(th);
596 columns++;
597
598 let editable = (eq.settings &&
599 eq.settings.editable &&
600 eq.settings.editable.toLowerCase().includes(title.toLowerCase()));
601
602 // enable load/import if we have editable variables
603 if (editable && bButtons) {
604 document.getElementById('btnLoad').style.display = "inline";
605 document.getElementById('btnImport').style.display = "inline";
606 }
607
608 equipment.vars.push({
609 'name': title,
610 'editable': editable,
611 'subdir': subdir
612 });
613 }
614
615 if (eq.settings["history buttons"]) {
616 let th = document.createElement('th');
617 th.innerHTML = "HST";
618 th.className = "mtableheader mvarheader";
619 tr.appendChild(th);
620 columns++;
621 }
622
623 for (let i = 0; i < nRows; i++) {
624 if (equipment.table.length <= i)
625 equipment.table.push({});
626
627 equipment.table[i].selected = false;
628 equipment.table[i].lastSelected = false;
629
630 // create new row, install listener and set style
631 let tr = table.insertRow();
632
633 tr.id = "spTr";
634 if (cpd && cpd > 0)
635 tr.classList.add("p" + Math.floor(i / cpd));
636
637 tr.addEventListener('mousedown', mouseEvent);
638 tr.addEventListener('mousemove', mouseEvent);
639 tr.addEventListener('mouseout', mouseEvent);
640
641 // catch all mouseup events to stop dragging
642 document.addEventListener('mouseup', mouseEvent);
643
644 tr.style.userSelect = 'none';
645
646 if (groupDisplay) {
647 let group = eq.settings.names[i];
648 group = group.substring(0, group.indexOf('%'));
649 tr.setAttribute('name', group);
650 if (groupList.length > 0 && group !== groupList[0])
651 tr.style.display = "none";
652 }
653
654 equipment.table[i].tr = tr;
655
656 // iterate over all variables in equipment
657 for (let v of equipment.vars) {
658
659 let td = tr.insertCell();
660 td.style.textAlign = "right";
661
662 if (eq.settings && eq.settings.group)
663 if (eq.settings.group[i])
664 td.style.borderTop = "2px solid #808080";
665
666 if (v.name === "#") {
667 td.innerHTML = i.toString();
668 td.style.width = "10px";
669 } else if (v.name === "Names") {
670 td.style.textAlign = "center";
671 if (eq.settings && eq.settings.names) {
672 if (groupDisplay)
673 td.innerHTML = eq.settings.names[i].substring(eq.settings.names[i].indexOf('%') + 1);
674 else
675 td.innerHTML = eq.settings.names[i];
676 }
677 } else {
678 let format = eq.settings && eq.settings['format ' + v.name.toLowerCase()];
679 if (format === undefined || format === null)
680 format = "%f2"; // default format
681 else if (Array.isArray(format))
682 format = format[i];
683
684 if (v.editable) {
685
686 let boolean = false;
687 let options = [];
688 if (v.subdir.includes("Variables")) {
689 boolean = typeof eq.variables[v.name.toLowerCase()] === 'boolean' ||
690 (typeof eq.variables[v.name.toLowerCase()] === 'object' &&
691 typeof eq.variables[v.name.toLowerCase()][0] === 'boolean');
692 } else if (v.subdir.includes("Settings")) {
693 boolean = typeof eq.settings[v.name.toLowerCase()] === 'boolean' ||
694 (typeof eq.settings[v.name.toLowerCase()] === 'object' &&
695 typeof eq.settings[v.name.toLowerCase()][0] === 'boolean');
696 }
697 if (eq.settings["options " + v.name.toLowerCase()]) {
698 options = eq.settings["options " + v.name.toLowerCase()];
699 }
700
701 if (boolean) {
702 td.innerHTML = "<select class=\"modbselect\" " +
703 "data-odb-path=\"/Equipment/" + equipment.name + v.subdir + v.name + "[" + i + "]\" " +
704 "data-validate='validateData'>" +
705 "<option value='false'>No</option>" +
706 "<option value='true'>Yes</option>" +
707 "</select>";
708 } else if (options.length > 0) {
709 let html = "<select class=\"modbselect\" " +
710 "data-odb-path=\"/Equipment/" + equipment.name + v.subdir + v.name + "[" + i + "]\" " +
711 "data-validate='validateData'>";
712
713 options.forEach(opt => {
714 html += `<option value="${opt}">${opt}</option>`;
715 });
716
717 html += "</select>";
718
719 td.innerHTML = html;
720 } else {
721 td.style.width = "90px";
722 td.style.height = "22px";
723 td.innerHTML = "<div class=\"modbvalue\" " +
724 "data-odb-path=\"/Equipment/" + equipment.name + v.subdir + v.name + "[" + i + "]\" " +
725 "data-odb-editable='1' " +
726 "data-size='10' " +
727 "data-format=\"" + format + "\" " +
728 "data-validate='validateData' " +
729 "onchange='valueChanged(this);' " +
730 "></div>";
731 }
732 } else
733 td.innerHTML = "<div class=\"modbvalue\" " +
734 "data-odb-path=\"/Equipment/" + equipment.name + v.subdir + v.name + "[" + i + "]\" " +
735 "data-format=\"" + format + "\" " +
736 "onchange='valueChanged(this);' " +
737 "></div>";
738
739 // Unit
740 if (eq.settings && eq.settings['unit ' + v.name.toLowerCase()]) {
741
742 let unit = eq.settings['unit ' + v.name.toLowerCase()];
743 if (Array.isArray(unit))
744 unit = unit[i];
745
746 td = tr.insertCell();
747 td.style.width = "10px";
748
749 if (eq.settings && eq.settings.group)
750 if (eq.settings.group[i])
751 td.style.borderTop = "2px solid #808080";
752
753 td.innerHTML = unit;
754
755 } else
756 td.style.textAlign = 'center';
757 }
758 }
759
760 if (eq.settings['history buttons']) {
761
762 td = tr.insertCell();
763 td.style.width = "10px";
764 td.style.textAlign = 'center';
765
766 if (eq.settings && eq.settings.group)
767 if (eq.settings.group[i])
768 td.style.borderTop = "2px solid #808080";
769
770 let str = "";
771 for (let v of equipment.vars) {
772 let vn = eq.variables[v.name.toLowerCase()];
773 if (vn && (typeof vn === "number" || Array.isArray(vn) && typeof vn[0] === "number")) {
774 if (eq.settings.names && Array.isArray(eq.settings.names))
775 str += equipment.name + ":" + eq.settings.names[i] + " " + v.name + ",";
776 else
777 str += equipment.name + ":" + v.name + "[" + i + "],";
778 }
779 }
780 if (str.length > 0)
781 str = str.slice(0, -1);
782
783 let title = Array.isArray(eq.settings.names) ? eq.settings.names[i] : v.name;
784 td.innerHTML = "<button style='padding: 0'" +
785 " onclick=\"hist(\'" + title + "\',\'" + str + "\');\">" +
786 " <img src='icons/activity.svg' title='Show graph'>" +
787 "</button>";
788 }
789
790 document.getElementById(equipment.tid).style.visibility = '';
791
792 if (cpd && cpd > 0) {
793 enable_row(tr, eq.settings.enabled, Math.floor(i / cpd));
794 } else {
795 if (eq.settings === undefined)
796 enable_row(tr, true, i);
797 else {
798 if (Array.isArray(eq.settings.enabled))
799 enable_row(tr, eq.settings.enabled[i], i);
800 else
801 enable_row(tr, eq.settings.enabled, i);
802 }
803 }
804 }
805
806 if (cpd && cpd > 0)
807 pageSel(eq.devNames[0]);
808
809 } else { // ------------ non-grid display
810
811 let n = 0;
812
813 for (const e in eq.variables) {
814 if (!e.includes('/name'))
815 continue;
816
817 let vName = eq.variables[e];
818 let vArr = eq.variables[vName.toLowerCase()];
819 let editable;
820 if (eq.settings && eq.settings.editable) {
821 if (Array.isArray(eq.settings.editable))
822 editable = eq.settings.editable;
823 else
824 editable = eq.settings.editable.toLowerCase().includes(vName.toLowerCase());
825 }
826
827 // skip subdirectories
828 if (typeof vArr === 'object' && !Array.isArray(vArr))
829 continue;
830
831 let flag = editable;
832 if (Array.isArray(editable))
833 flag = editable.includes(true);
834
835 equipment.vars.push({
836 'name': vName,
837 'editable': flag
838 });
839
840 // enable load/import if we have editable variables
841 if (flag && bButtons) {
842 document.getElementById('btnLoad').style.display = "inline";
843 document.getElementById('btnImport').style.display = "inline";
844 }
845
846 let tr = table.insertRow();
847
848 // Index
849 let th = document.createElement('th');
850 th.className = "mtableheader mvarheader";
851 th.innerHTML = "#";
852 tr.appendChild(th);
853 columns = 1;
854
855 // Name
856 if (eq.settings && eq.settings['names ' + vName.toLowerCase()] !== undefined) {
857 let th = document.createElement('th');
858 th.className = "mtableheader mvarheader";
859 th.innerHTML = "Name";
860 tr.appendChild(th);
861 columns = 2;
862 }
863
864 th = document.createElement('th');
865 th.innerHTML = vName;
866 th.className = "mtableheader mvarheader";
867 th.colSpan = 2;
868 tr.appendChild(th);
869 columns += 2;
870
871 if (eq.settings && eq.settings["history buttons"]) {
872 let th = document.createElement('th');
873 th.innerHTML = "HST";
874 th.className = "mtableheader mvarheader";
875 tr.appendChild(th);
876 columns++;
877 }
878
879 // variables
880 if (!Array.isArray(vArr))
881 vArr = [vArr];
882 for (let i = 0; i < vArr.length; i++) {
883
884 equipment.table.push({});
885
886 equipment.table[n].selected = false;
887 equipment.table[n].lastSelected = false;
888
889 // create new row, install listener and set style
890 let tr = table.insertRow();
891
892 tr.id = "spTr";
893 tr.addEventListener('mousedown', mouseEvent);
894 tr.addEventListener('mousemove', mouseEvent);
895 tr.addEventListener('mouseout', mouseEvent);
896
897 // catch all mouseup events to stop dragging
898 document.addEventListener('mouseup', mouseEvent);
899
900 tr.style.userSelect = 'none';
901
902 if (groupDisplay) {
903 let group = eq.settings.names[i];
904 group = group.substring(0, group.indexOf('%'));
905 tr.setAttribute('name', group);
906 if (groupList.length > 0 && group !== groupList[0])
907 tr.style.display = "none";
908 }
909
910 equipment.table[n].tr = tr;
911
912 // index and name
913 if (eq.settings && eq.settings['names ' + vName.toLowerCase()] !== undefined) {
914 td = tr.insertCell();
915 if (eq.settings && eq.settings.group)
916 if (eq.settings.group[i])
917 td.style.borderTop = "2px solid #808080";
918
919 td.innerHTML = i;
920
921 td = tr.insertCell();
922 if (eq.settings && eq.settings.group)
923 if (eq.settings.group[i])
924 td.style.borderTop = "2px solid #808080";
925
926 if (Array.isArray(eq.settings['names ' + vName.toLowerCase()])) {
927 td.innerHTML = eq.settings['names ' + vName.toLowerCase()][i]
928 } else {
929 td.innerHTML = eq.settings['names ' + vName.toLowerCase()];
930 }
931 } else {
932 td = tr.insertCell();
933
934 if (eq.settings && eq.settings.group)
935 if (eq.settings.group[i])
936 td.style.borderTop = "2px solid #808080";
937
938 td.innerHTML = vName + '[' + i + ']';
939 }
940
941 // units
942 let unit;
943 if (eq.settings && Array.isArray(eq.settings['unit ' + vName.toLowerCase()]))
944 unit = eq.settings['unit ' + vName.toLowerCase()];
945 else if (eq.settings && eq.settings['unit ' + vName.toLowerCase()])
946 unit = new Array(vArr.length).fill(eq.settings['unit ' + vName.toLowerCase()]);
947
948 // value
949 td = tr.insertCell();
950
951 if (eq.settings && eq.settings.group)
952 if (eq.settings.group[i])
953 td.style.borderTop = "2px solid #808080";
954
955 let format;
956 if (eq.settings)
957 format = eq.settings['format ' + vName.toLowerCase()];
958 if (format === undefined || format === null)
959 format = new Array(vArr.length).fill("%f2"); // default format
960
961 let flag = editable;
962 if (Array.isArray(flag))
963 flag = flag[n];
964
965 if (flag) {
966 td.style.width = "90px";
967 td.style.height = "22px";
968 td.style.textAlign = "right";
969 td.innerHTML = "<div class=\"modbvalue\" " +
970 "data-odb-path=\"/Equipment/" + equipment.name + "/Variables/" + vName + "[" + i + "]\" " +
971 "data-odb-editable='1' " +
972 "data-size='10' " +
973 "data-format=\"" + format[i] + "\" " +
974 "onchange='valueChanged(this);' " +
975 "></div>";
976 } else {
977 td.style.textAlign = "right";
978 td.innerHTML = "<div class=\"modbvalue\" " +
979 "data-odb-path=\"/Equipment/" + equipment.name + "/Variables/" + vName + "[" + i + "]\" " +
980 "data-format=\"" + format[i] + "\" " +
981 "onchange='valueChanged(this);' " +
982 "></div>";
983 }
984
985 td = tr.insertCell();
986 td.style.width = "10px";
987
988 if (eq.settings && eq.settings.group)
989 if (eq.settings.group[i])
990 td.style.borderTop = "2px solid #808080";
991
992 if (unit)
993 td.innerHTML = unit[i];
994 else
995 td.innerHTML = " ";
996
997 if (eq.settings && eq.settings['history buttons']) {
998
999 td = tr.insertCell();
1000 td.style.width = "10px";
1001 td.style.textAlign = 'center';
1002
1003 if (eq.settings && eq.settings.group)
1004 if (eq.settings.group[i])
1005 td.style.borderTop = "2px solid #808080";
1006
1007 let str = "";
1008 if (eq.settings && eq.settings['names'] !== undefined) {
1009 str = eq.settings['names'][i];
1010 } else if (eq.settings && eq.settings['names ' + vName.toLowerCase()] !== undefined) {
1011 if (Array.isArray(eq.settings['names ' + vName.toLowerCase()])) {
1012 str = eq.settings['names ' + vName.toLowerCase()][i]
1013 } else {
1014 str = eq.settings['names ' + vName.toLowerCase()];
1015 }
1016 } else {
1017 str = vName + '[' + i + ']';
1018 }
1019
1020 td.innerHTML = "<button style='padding: 0'" +
1021 " onclick=\"hist(\'" + equipment.name + ":" + str + "\');\">" +
1022 " <img src='icons/activity.svg' title='Show graph'>" +
1023 "</button>";
1024 }
1025
1026
1027 n++;
1028 }
1029 }
1030
1031 document.getElementById(equipment.tid).style.visibility = '';
1032 }
1033
1034 if (document.getElementById('fewarning'))
1035 document.getElementById('fewarning').colSpan = columns;
1036 if (document.getElementById('tableHeader'))
1037 document.getElementById('tableHeader').colSpan = columns;
1038 if (document.getElementById('menuButtons'))
1039 document.getElementById('menuButtons').colSpan = columns;
1040 if (groupDisplay)
1041 document.getElementById('groupSelector').colSpan = columns;
1042 if (document.getElementById('displayExtra'))
1043 document.getElementById('displayExtra').colSpan = columns;
1044 if (document.getElementById('pageSelector'))
1045 document.getElementById('pageSelector').colSpan = columns;
1046
1047 // populate equipment table
1048 if (bEqSelect) {
1049 mjsonrpc_db_ls(["/Equipment"]).then(
1050 rpc => {
1051 let eqList = rpc.result.data[0];
1052 let sel = document.getElementById('eqSelect');
1053 for (eq in eqList) {
1054 let o = document.createElement('option');
1055 o.text = eq;
1056 sel.add(o);
1057 }
1058 sel.value = equipment.name;
1059 });
1060 }
1061
1062 let d = document.getElementById(equipment.tid);
1063 if (d) {
1064 d = d.parentNode.parentNode.parentNode;
1065 if (d.tagName === 'DIV')
1066 dlgCenter(d);
1067 }
1068
1069 check_frontend();
1070
1071 document.addEventListener('mousedown', removeSelection);
1072 mhttpd_refresh();
1073 });
1074}
1075
1076let allowIncDec = false;
1077
1078function saveFilePicker() {
1079 file_picker("equipment", "*.json", saveFile, true, equipment.name, true);
1080}
1081
1082function saveFile(filename) {
1083 mjsonrpc_db_copy(['/Equipment/' + equipment.name + '/Variables']).then(rpc => {
1084
1085 if (filename.indexOf('.json') === -1)
1086 filename += '.json';
1087
1088 let header = {
1089 "/MIDAS version": "2.1",
1090 "/filename": filename,
1091 "/ODB path": "/Equipment/" + equipment.name + "/Variables"
1092 }
1093 header = JSON.stringify(header, null, ' ');
1094 header = header.substring(0, header.length-2) + ','; // strip trailing '}'
1095
1096 let odbJson = JSON.stringify(rpc.result.data[0], null, ' ');
1097 if (odbJson.indexOf('{') === 0)
1098 odbJson = odbJson.substring(1); // strip leading '{'
1099
1100 odbJson = header + odbJson;
1101
1102 file_save_ascii(filename, odbJson, "Equipment \"" + equipment.name +
1103 "\" saved to file \"" + filename + "\"");
1104
1105 }).catch(error => mjsonrpc_error_alert(error));
1106}
1107
1108function exportFileQuery() {
1109 dlgQuery("Enter filename: ", "default.json", exportFile);
1110}
1111
1112function exportFile(filename) {
1113 if (filename === false)
1114 return;
1115 mjsonrpc_db_copy(['/Equipment/' + equipment.name + '/Variables']).then(rpc => {
1116
1117 if (filename.indexOf('.json') === -1)
1118 filename += '.json';
1119
1120 let header = {
1121 "/MIDAS version": "2.1",
1122 "/filename": filename,
1123 "/ODB path": "/Equipment/" + equipment.name + "/Variables"
1124 }
1125 header = JSON.stringify(header, null, ' ');
1126 header = header.substring(0, header.length-2) + ','; // strip trailing '}'
1127
1128 let odbJson = JSON.stringify(rpc.result.data[0], null, ' ');
1129 if (odbJson.indexOf('{') === 0)
1130 odbJson = odbJson.substring(1); // strip leading '{'
1131
1132 odbJson = header + odbJson;
1133
1134 // use trick from FileSaver.js
1135 let a = document.getElementById('downloadHook');
1136 if (a === null) {
1137 a = document.createElement("a");
1138 a.style.display = "none";
1139 a.id = "downloadHook";
1140 document.body.appendChild(a);
1141 }
1142
1143 let blob = new Blob([odbJson], {type: "text/json"});
1144 let url = window.URL.createObjectURL(blob);
1145
1146 a.href = url;
1147 a.download = filename;
1148 a.click();
1149 window.URL.revokeObjectURL(url);
1150 dlgAlert("Equipment \"" + equipment.name +
1151 "\" downloaded to file \"" + filename + "\"");
1152
1153 }).catch(error => mjsonrpc_error_alert(error));
1154}
1155
1156function loadFilePicker() {
1157 file_picker("equipment", "*.json", loadFile);
1158}
1159
1160function loadFile(filename) {
1161 file_load_ascii(filename, (text) => fileInterprete(filename, text) );
1162}
1163
1164async function importFile() {
1165 // Chrome has file picker, others might have not
1166 try {
1167 let fileHandle;
1168 [fileHandle] = await window.showOpenFilePicker();
1169 const file = await fileHandle.getFile();
1170 const text = await file.text();
1171 fileInterprete(file.name, text);
1172 } catch (error) {
1173 if (error.name !== 'AbortError') {
1174 // fall-back to old method
1175 if (document.getElementById('dlgFileSelect') === null) {
1176 let fsDiv = document.createElement('div');
1177 fsDiv.id = "dlgFileSelect";
1178 fsDiv.className = "dlgFrame";
1179 fsDiv.innerHTML = dlgFileSelect;
1180 document.body.appendChild(fsDiv);
1181 }
1182 dlgShow('dlgFileSelect', true);
1183 }
1184 }
1185}
1186
1187function importFileFromSelector() {
1188
1189 let input = document.getElementById('fileSelector');
1190 let file = input.files[0];
1191 if (file !== undefined) {
1192 let reader = new FileReader();
1193 reader.readAsText(file);
1194
1195 reader.onerror = function () {
1196 dlgAlert('File read error: ' + reader.error);
1197 }
1198
1199 reader.onload = function () {
1200 fileInterprete(file.name, reader.result);
1201 }
1202 }
1203}
1204
1205function fileInterprete(filename, text) {
1206 try {
1207 let j = JSON.parse(text);
1208
1209 // check for correct equipment
1210 let path = j["/ODB path"].split('/');
1211 if (path[2] !== equipment.name) {
1212 dlgAlert("File contains settings for equipment \"" + path[2] + "\"<br />"+
1213 "and therefore cannot be loaded into equipment \"" + equipment.name +"\"");
1214 return;
1215 }
1216
1217 mhttpd_refresh_pause(true);
1218
1219 for (let v of equipment.vars)
1220 if (v.editable)
1221 modbset("/Equipment/" + equipment.name + "/Variables/" + v.name, j[v.name]);
1222
1223 mhttpd_refresh_pause(false);
1224
1225 window.setTimeout(mhttpd_refresh, 10);
1226
1227 mjsonrpc_cm_msg("Values loaded from file \"" + filename + "\"");
1228 } catch (error) {
1229 dlgAlert("File \"" + filename + "\" is not a valid JSON file:<br /><br />" + error);
1230 }
1231}
1232
1233function renderSelection() {
1234 // change color according to selection
1235 for (let i=0 ; i<equipment.table.length ; i++) {
1236 if (equipment.table[i].selected) {
1237 equipment.table[i].tr.style.backgroundColor = '#004CBD';
1238 equipment.table[i].tr.style.color = '#FFFFFF';
1239
1240 // change <a> color
1241 if (equipment.table[i].tr.getElementsByTagName('a').length > 0)
1242 equipment.table[i].tr.getElementsByTagName('a')[0].style.color = '#FFFFFF';
1243 } else {
1244
1245 let index = i;
1246
1247 if (equipment.channelsPerDevice)
1248 index = Math.floor(i / equipment.channelsPerDevice);
1249 enable_row(equipment.table[i].tr, equipment.enabled, index);
1250
1251 equipment.table[i].tr.style.color = ''; // text is black again
1252
1253 // change <a> color
1254 let a = equipment.table[i].tr.getElementsByTagName('a');
1255 if (a.length > 0) {
1256 a = a[0];
1257 a.style.color = '';
1258 }
1259 }
1260 }
1261}
1262
1263let mouseDragged = false;
1264
1265// function gets called from document
1266function removeSelection(e) {
1267 // don't de-select if we click on "->" or on drop-down box
1268 if (e.target.tagName === 'BUTTON' || e.target.tagName === 'SELECT')
1269 return;
1270
1271 for (let i = 0; i < equipment.table.length; i++) {
1272 equipment.table[i].selected = false;
1273 equipment.table[i].lastSelected = false;
1274 }
1275 renderSelection();
1276}
1277
1278function getSelectedElements() {
1279 let sel = [];
1280 for (let i = 0; i < equipment.table.length; i++)
1281 if (equipment.table[i].selected)
1282 sel.push(i);
1283
1284 return sel;
1285}
1286
1287function mouseEvent(e) {
1288
1289 if (e.type === 'mouseup')
1290 mouseDragged = false;
1291
1292 // keep off secondary mouse buttons
1293 if (e.button !== 0)
1294 return;
1295
1296 // keep off drop down boxes
1297 if (e.target.tagName === 'SELECT')
1298 return;
1299
1300 e.preventDefault();
1301 e.stopPropagation();
1302
1303 // find current row
1304 let index;
1305 for (index = 0; index < equipment.table.length; index++)
1306 if (equipment.table[index].tr === e.target ||
1307 equipment.table[index].tr.contains(e.target))
1308 break;
1309 if (index === equipment.table.length)
1310 index = undefined;
1311
1312 // ignore clicks to <button>'a and <a>'s
1313 if (e.target.tagName === 'A' || e.target.tagName === 'BUTTON' || e.target.tagName === 'IMG')
1314 return;
1315
1316 if (e.type === 'mousedown') {
1317 mouseDragged = true;
1318
1319 if (e.shiftKey) {
1320
1321 // search last selected element
1322 let i1;
1323 for (i1 = 0 ; i1 < equipment.table.length ; i1++)
1324 if (equipment.table[i1].lastSelected)
1325 break;
1326 if (i1 === equipment.table.length)
1327 i1 = 0; // none selected, so use first one
1328
1329 if (i1 > index)
1330 [i1, index] = [index, i1];
1331
1332 for (let i=i1 ; i<= index ; i++)
1333 if (!equipment.table[i].tr.disabled)
1334 equipment.table[i].selected = true;
1335
1336 } else if (e.metaKey || e.ctrlKey) {
1337
1338 console.log("index = " + index);
1339
1340 // just toggle current selection
1341 if (!equipment.table[index].tr.disabled)
1342 equipment.table[index].selected = !equipment.table[index].selected;
1343
1344 // remember which row was last selected
1345 for (let i=0 ; i<equipment.table.length ; i++)
1346 equipment.table[i].lastSelected = false;
1347 if (equipment.table[index].selected)
1348 equipment.table[index].lastSelected = true;
1349
1350 } else {
1351 // no key pressed -> un-select all but current
1352 for (let i = 0; i < equipment.table.length; i++) {
1353 equipment.table[i].selected = false;
1354 equipment.table[i].lastSelected = false;
1355 }
1356
1357 if (!equipment.table[index].tr.disabled) {
1358 equipment.table[index].selected = true;
1359 equipment.table[index].lastSelected = true;
1360 }
1361 }
1362
1363 renderSelection();
1364 }
1365
1366 if (e.type === 'mousemove' && mouseDragged) {
1367
1368 // don't do dragging if shift or ctrl key pressed
1369 if (!e.shiftKey && !e.metaKey && !e.ctrlKey) {
1370 // unselect all
1371 for (let i = 0; i < equipment.table.length; i++)
1372 equipment.table[i].selected = false;
1373
1374 // current row
1375 let i2;
1376 for (i2 = 0; i2 < equipment.table.length; i2++)
1377 if (equipment.table[i2].tr === e.target ||
1378 equipment.table[i2].tr.contains(e.target))
1379 break;
1380
1381 if (i2 < equipment.table.length) {
1382 // search last selected element
1383 let i1;
1384 for (i1 = 0; i1 < equipment.table.length; i1++)
1385 if (equipment.table[i1].lastSelected)
1386 break;
1387 if (i1 === equipment.table.length)
1388 i1 = 0; // none selected, so use first one
1389
1390 if (i1 > i2)
1391 [i1, i2] = [i2, i1];
1392
1393 for (let i = i1; i <= i2; i++)
1394 if (!equipment.table[i].tr.disabled)
1395 equipment.table[i].selected = true;
1396
1397 renderSelection();
1398 }
1399 }
1400 }
1401}
1402
1403function dlgEquipment(eqName, bButtons = false, bEqSelect = false) {
1404
1405 // check if equipment exists in ODB
1406 mjsonrpc_db_ls(['/Equipment/' + eqName]).then(
1407 rpc => {
1408 let eq = rpc.result.data[0];
1409 if (eq === null) {
1410 dlgAlert('dlgEquipment: Equipment \"' + eqName + '\" does not exist in ODB');
1411 return;
1412 }
1413
1414 if (eq.variables === null) {
1415 dlgAlert('dlgEquipment: No valid \"/Equipment/' + eqName + '/Variables\" in ODB');
1416 return;
1417 }
1418
1419 let d = document.getElementById('dlgEqTitle');
1420
1421 // If dialog is already there, reuse it.
1422 if (!d) {
1423 d = document.createElement("div");
1424 d.className = "dlgFrame";
1425 d.style.zIndex = "30";
1426 d.shouldDestroy = true;
1427
1428 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgEqTitle\">" + eqName + "</div>" +
1429 "<div class=\"dlgPanel\" style=\"padding: 4px;\">" + "<div id=\"dlgEquipment\">" + "</div></div>";
1430 document.body.appendChild(d);
1431 dlgShow(d);
1432 }
1433
1434 eqtable_init(document.getElementById('dlgEquipment'), eqName, bButtons, bEqSelect);
1435
1436 if (bEqSelect) {
1437 let eqSelect = document.getElementById('eqSelect');
1438 eqSelect.onchange = (event) => {
1439 redirectEq(document.getElementById('dlgEquipment'), event.target.value);
1440 };
1441 }
1442 });
1443
1444}
1445
1446