MIDAS
Loading...
Searching...
No Matches
controls.js
Go to the documentation of this file.
1//
2// controls.js
3// Custom Controls
4//
5// Created by Stefan Ritt on 5/8/15.
6//
7
8/*
9
10 Usage
11 =====
12
13 Dialog boxes
14 ------------
15
16 Dialog boxes consists of normal HTML code defined on the current web page. By
17 using the class "dlgFrame" they are automatically hidden until the
18 dlgShow() function is called. The dialog has a title bar and can be moved
19 around by dragging the title bar. The dlgHide() function should be called
20 to close (hide) the dialog.
21
22 Following button shows the dialog dlgXXX:
23
24 <button type="button" onClick="dlgShow('dlgXXX')">XXX</button>
25
26 Following HTML code defines the dialog dlgXXX:
27
28 <div id="dlgXXX" class="dlgFrame">
29 <div class="dlgTitlebar">Title</div>
30 <div class="dlgPanel">
31 <div>Dialog Contents</div>
32 <button class="dlgButton" onClick="dlgHide('dlgXXX')">Close</button>
33 </div>
34 </div>
35
36
37 Standard dialog boxes
38 ---------------------
39
40 dlgAlert(message)
41 Replacement for alert() function showing a standard notification
42
43 dlgConfirm(message, callback, param)
44 Replacement of confirm() dialog. Shows a message dialog with a 'Cancel'
45 and 'Ok' button. The callback is called with the first parameter either
46 true (if Ok has been clicked) or false (if Cancel has been clicked) and
47 the second parameter a copy of 'param' passed to dlgConfirm().
48
49 dlgQuery(message, value, callback, param)
50 Replacement of prompt() dialog. Shows a dialog box ith a 'Cancel', 'Ok'
51 button and a field to enter a value. 'message' is shown before the
52 input filed and can contain a string like 'Please enter value:'. If
53 'cancel' is pressed, the 'callback' function is called with the first
54 parameter equal 'false'. If 'Ok' is pressed, 'callback' is called with
55 the first parameter being the value of the input field. 'param' is just
56 passed to the callback function as an optional second parameter. So a
57 typical callback function can look like
58
59 function cb(value, param) {
60 if (value !== false)
61 alert('Value is '+value+', param is '+param);
62 }
63
64 where 'param' can also be ommitted.
65
66 dlgMessage(title, message, modal, error, callback, param)
67 Similar to dlgAlert, but with the option to set a custom title which
68 gets a red background if error is true. After the 'Ok' button is pressed,
69 the callback function is called with an optional parameter passed to
70 dlgMessage. If modal equals true, the whole screen is greyed out and all
71 mouse events are captured until the 'Ok' button is pressed.
72
73 dlgWait(time, string)
74 Shows a model dialog with a progress bar for 'time' seconds and 'string'
75 in the first line. After 'time' seconds the dialog closes automatically.
76
77 Sliders
78 -------
79
80 <button name="ctrlHSlider" type="button" class="ctrlHSlider" data-update="xxx()" id="yyy"></button>
81
82 <button name="ctrlVSlider" type="button" class="ctrlVSlider" data-update="xxx()" id="yyy"></button>
83
84 On each change of the slider, the function xxx(value, final) is called with
85 value ranging from 0 to 1. Dragging the slider will cause many updates with
86 final = false, and once the mouse button got released, the funciton is called
87 once with final = true.
88
89 To set the slider programmatically call
90
91 document.getElementById('yyy').set(0.5);
92
93 Valid range is again 0 to 1.
94
95
96 Progress bars
97 -------------
98
99 <div name="ctrlProgress" style="width:xxxpx;color:yyy;" id="zzz"></div>
100
101 document.getElementById('yyy').set(v); // v = 0...1
102
103
104
105 Icon buttons
106 ------------
107
108 <button name="ctrlButton" data-draw="xxx" type="button" id="yyy" onClick="zzz()"></button>
109
110 function xxx(cvs)
111 {
112 // example for an up-arrow with 36x36 pixels
113 cvs.width = 36;
114 cvs.height = 36;
115 let ctx = cvs.getContext("2d");
116 ctx.fillStyle = "#E0E0E0";
117 ctx.fillRect(0, 0, 36, 36);
118 ctx.beginPath();
119 ctx.moveTo(18, 7);
120 ctx.lineTo(31, 27);
121 ctx.lineTo(5, 27);
122 ctx.lineTo(18, 7);
123 ctx.closePath();
124 ctx.fillStyle = "#808080";
125 ctx.fill();
126 }
127
128*/
129
130// default styles for dialog boxes
131let controls_css = `<style>
132 .dlgFrame {
133 font-family: verdana,tahoma,sans-serif;
134 border: 1px solid black;
135 box-shadow: 6px 6px 10px 4px rgba(0,0,0,0.2);
136 border-radius: 6px;
137 position: absolute;
138 top: 0;
139 left: 0;
140 z-index: 10;
141 display: none; /* pre-hidden */
142 }
143 .dlgTitlebar {
144 user-select: none;
145 text-align: center;
146 background-color: #C0C0C0;
147 border-top-left-radius: 6px;
148 border-top-right-radius: 6px;
149 font-size: 10pt;
150 padding: 2px;
151 padding-left: 30px;
152 padding-right: 30px;
153 }
154 .dlgTitlebar:hover {
155 cursor: pointer;
156 }
157 .dlgButton {
158 font-size: 10pt;
159 background-color: #F8F8F8;
160 border: 1px solid #808080;
161 border-radius: 6px;
162 padding: 2px 4px;
163 margin: 3px;
164 }
165 .dlgButtonDefault {
166 font-size: 10pt;
167 background-color: #4187F7;
168 color: #FFFFFF;
169 border: 1px solid #808080;
170 border-radius: 6px;
171 padding: 2px 4px;
172 margin: 3px;
173 }
174 .dlgButton:hover {
175 background-color: #F0F0F0;
176 }
177 .dlgPanel {
178 background-color: #F0F0F0;
179 text-align: center;
180 padding: 4px;
181 border-bottom-left-radius: 6px;
182 border-bottom-right-radius: 6px;
183 }
184 .dlgBlackout {
185 background: rgba(0,0,0,.5);
186 position: fixed;
187 top: 0;
188 left: 0;
189 bottom: 0;
190 right: 0;
191 z-index: 20;
192 }
193 .ctrlHSlider {
194 width: 200px;
195 height: 30px;
196 border-radius: 5px;
197 padding: 0;
198 }
199 .ctrlVSlider {
200 width: 20px;
201 height: 200px;
202 border-radius: 5px;
203 padding: 0;
204 }
205 .ctrlProgress {
206 border: 1px solid #A0A0A0;
207 border-radius: 5px;
208 width: 500px;
209 height: 5px;
210 background-color: #E0E0E0;
211 margin-left: 6px;
212 margin-top: 5px;
213 }
214 .ctrlProgressInd {
215 border-radius: 5px;
216 width: 0;
217 height: 5px;
218 background-color: #419bf9;
219 margin: 0;
220 }
221 </style>`;
222
223(function (window) { // anonymous global function
224 window.addEventListener("load", ctlInit, false);
225})(window);
226
227function ctlInit() {
228 let CTL = new Controls();
229 CTL.init();
230}
231
232function Controls() // constructor
233{
234}
235
236Controls.prototype.init = function () // scan DOM
237{
238 // add special style
239 document.head.insertAdjacentHTML("beforeend", controls_css);
240
241 // scan DOM for controls
242 this.ctrlButton = document.getElementsByName("ctrlButton");
243 this.ctrlVSlider = document.getElementsByName("ctrlVSlider");
244 this.ctrlHSlider = document.getElementsByName("ctrlHSlider");
245 this.ctrlProgress = document.getElementsByName("ctrlProgress");
246
247 // ctrlButton
248 for (let i = 0; i < this.ctrlButton.length; i++) {
249 let cvs = document.createElement("canvas");
250 this.ctrlButton[i].appendChild(cvs);
251
252 if (this.ctrlButton[i].dataset.draw !== undefined)
253 eval(this.ctrlButton[i].dataset.draw + "(cvs)");
254 }
255
256 // ctrlVSlider
257 for (let i = 0; i < this.ctrlVSlider.length; i++) {
258 let cvs = document.createElement("canvas");
259 let sl = this.ctrlVSlider[i];
260 cvs.width = sl.clientWidth;
261 cvs.height = sl.clientHeight;
262 sl.appendChild(cvs);
263 sl.canvas = cvs;
264
265 sl.position = 0.5; // slider position 0...1
266 sl.addEventListener("click", this.ctrlVSliderHandler.bind(this));
267 sl.addEventListener("contextmenu", this.ctrlVSliderHandler.bind(this));
268 sl.addEventListener("mousemove", this.ctrlVSliderHandler.bind(this));
269 sl.addEventListener("touchmove", this.ctrlVSliderHandler.bind(this));
270 sl.draw = this.ctrlVSliderDraw;
271 sl.draw(sl);
272 sl.set = this.ctrlVSliderSet;
273 }
274
275 // ctrlHSlider
276 for (let i = 0; i < this.ctrlHSlider.length; i++) {
277 let cvs = document.createElement("canvas");
278 let sl = this.ctrlHSlider[i];
279 cvs.width = sl.clientWidth;
280 cvs.height = sl.clientHeight;
281 sl.appendChild(cvs);
282 sl.canvas = cvs;
283
284 sl.position = 0.5; // slider position 0...1
285 sl.addEventListener("click", this.ctrlHSliderHandler.bind(this));
286 sl.addEventListener("contextmenu", this.ctrlHSliderHandler.bind(this));
287 sl.addEventListener("mousemove", this.ctrlHSliderHandler.bind(this));
288 sl.addEventListener("touchmove", this.ctrlHSliderHandler.bind(this));
289 sl.addEventListener("mouseup", this.ctrlHSliderHandler.bind(this));
290 sl.draw = this.ctrlHSliderDraw;
291 sl.draw(sl);
292 sl.set = this.ctrlHSliderSet;
293 }
294
295 // ctrlProgress
296 for (let i = 0; i < this.ctrlProgress.length; i++) {
297 let p = this.ctrlProgress[i];
298 p.className = "ctrlProgress";
299 let ind = document.createElement("div");
300 ind.className = "ctrlProgressInd";
301 ind.style.height = p.style.height;
302 ind.style.backgroundColor = p.style.color;
303 p.appendChild(ind);
304 p.set = this.ctrlProgressSet;
305 }
306
307};
308
309Controls.prototype.ctrlVSliderDraw = function (b) {
310 if (b === undefined)
311 b = this;
312 let w = b.canvas.width;
313 let h = b.canvas.height;
314 b.sliderOfs = 20;
315
316 let ctx = b.canvas.getContext("2d");
317 ctx.fillStyle = "#E0E0E0";
318 ctx.fillRect(0, 0, b.canvas.width, b.canvas.height);
319
320 let knob = b.sliderOfs + (1 - b.position) * (h - 2 * b.sliderOfs);
321
322 ctx.strokeStyle = "#A0A0A0";
323 ctx.lineWidth = 3;
324 ctx.beginPath();
325 ctx.moveTo(w / 2, b.sliderOfs);
326 ctx.lineTo(w / 2, knob);
327 ctx.stroke();
328
329 ctx.strokeStyle = "#00A0FF";
330 ctx.beginPath();
331 ctx.moveTo(w / 2, knob);
332 ctx.lineTo(w / 2, h - b.sliderOfs);
333 ctx.stroke();
334
335 ctx.fillStyle = "#E0E0E0";
336 ctx.strokeStyle = "#C0C0C0";
337 ctx.beginPath();
338 ctx.arc(w / 2, knob, 10, 0, 2 * Math.PI);
339 ctx.stroke();
340 ctx.fill();
341};
342
343Controls.prototype.ctrlVSliderSet = function (pos) {
344 if (pos < 0)
345 pos = 0;
346 if (pos > 1)
347 pos = 1;
348 this.position = pos;
349 this.draw();
350};
351
352Controls.prototype.ctrlVSliderHandler = function (e) {
353 e.preventDefault();
354 let y = undefined;
355 let b = e.currentTarget;
356
357 if (b.canvas === undefined) // we can get events from parent node
358 return;
359
360 if ((e.buttons === 1 && e.type === "mousemove") || e.type === "click")
361 y = e.offsetY;
362 if (e.type === "touchmove")
363 y = e.changedTouches[e.changedTouches.length - 1].clientY - b.getBoundingClientRect().top;
364
365 if (e.type === "contextmenu") {
366 b.position = 0.5;
367 this.ctrlVSliderDraw(b);
368 let f = b.dataset.update;
369 if (f.indexOf("("))
370 f = f.substr(0, f.indexOf("("));
371 window[f](b.position);
372 } else {
373 if (y !== undefined) {
374 b.position = 1 - (y - b.sliderOfs) / (b.clientHeight - 2 * b.sliderOfs);
375 if (b.position < 0)
376 b.position = 0;
377 if (b.position > 1)
378 b.position = 1;
379 this.ctrlVSliderDraw(b);
380 let f = b.dataset.update;
381 if (f.indexOf("("))
382 f = f.substr(0, f.indexOf("("));
383 window[f](b.position);
384 }
385 }
386};
387
388Controls.prototype.ctrlHSliderDraw = function (b) {
389 if (b === undefined)
390 b = this;
391 let w = b.canvas.width;
392 let h = b.canvas.height;
393 b.sliderOfs = 20;
394
395 let ctx = b.canvas.getContext("2d");
396 ctx.fillStyle = "#E0E0E0";
397 ctx.fillRect(0, 0, b.canvas.width, b.canvas.height);
398
399 let knob = b.sliderOfs + (b.position) * (w - 2 * b.sliderOfs);
400
401 ctx.strokeStyle = "#A0A0A0";
402 ctx.lineWidth = 3;
403 ctx.beginPath();
404 ctx.moveTo(w - b.sliderOfs, h / 2);
405 ctx.lineTo(knob, h / 2);
406 ctx.stroke();
407
408 ctx.strokeStyle = "#00A0FF";
409 ctx.beginPath();
410 ctx.moveTo(knob, h / 2);
411 ctx.lineTo(b.sliderOfs, h / 2);
412 ctx.stroke();
413
414 ctx.fillStyle = "#E0E0E0";
415 ctx.strokeStyle = "#C0C0C0";
416 ctx.beginPath();
417 ctx.arc(knob, h / 2, 10, 0, 2 * Math.PI);
418 ctx.stroke();
419 ctx.fill();
420};
421
422Controls.prototype.ctrlHSliderSet = function (pos) {
423 if (pos < 0)
424 pos = 0;
425 if (pos > 1)
426 pos = 1;
427 this.position = pos;
428 this.draw();
429};
430
431Controls.prototype.ctrlHSliderHandler = function (e) {
432 e.preventDefault();
433 let x = undefined;
434 let b = e.currentTarget;
435
436 if (b.canvas === undefined) // we can get events from parent node
437 return;
438
439 if ((e.buttons === 1 && e.type === "mousemove") || e.type === "click")
440 x = e.offsetX;
441 if (e.type === "touchmove")
442 x = e.changedTouches[e.changedTouches.length - 1].clientX - b.getBoundingClientRect().left;
443
444 if (e.type === "contextmenu") {
445 b.position = 0.5;
446 b.contextMenu = true;
447 this.ctrlHSliderDraw(b);
448 let f = b.dataset.update;
449 if (f.indexOf("("))
450 f = f.substr(0, f.indexOf("("));
451 window[f](b.position, true);
452 }
453
454 if (x !== undefined) {
455 b.contextMenu = false;
456 b.position = (x - b.sliderOfs) / (b.clientWidth - 2 * b.sliderOfs);
457 if (b.position < 0)
458 b.position = 0;
459 if (b.position > 1)
460 b.position = 1;
461 this.ctrlHSliderDraw(b);
462 let f = b.dataset.update;
463 if (f.indexOf("("))
464 f = f.substr(0, f.indexOf("("));
465 window[f](b.position, false);
466 }
467
468 if (e.type === "mouseup" && !b.contextMenu) {
469 console.log(e.buttons);
470 let f = b.dataset.update;
471 if (f.indexOf("("))
472 f = f.substr(0, f.indexOf("("));
473 window[f](b.position, true);
474 }
475};
476
477Controls.prototype.ctrlProgressSet = function (value) {
478 if (value < 0)
479 value = 0;
480 if (value > 1)
481 value = 1;
482 this.firstChild.style.width = Math.round(parseInt(this.style.width) * value) + "px";
483};
484
485//-------------------------------------------------------------------------------------------------
486var dlgLoadedDialogs = [];
487
488function dlgLoad(url) {
489 // check if dialog already laoded
490 if (dlgLoadedDialogs.includes(url))
491 return;
492
493 dlgLoadedDialogs.push(url);
494
495 // load dialog via AJAX
496 return new Promise(function (resolve, reject) {
497 let xhr = new XMLHttpRequest();
498 xhr.onreadystatechange = function () {
499 if (xhr.readyState === 4) {
500 if (xhr.status === 200) {
501 let d = document.createElement("div");
502 d.innerHTML = xhr.responseText;
503 document.body.appendChild(d);
504 resolve(xhr.responseText);
505 } else {
506 dlgAlert("network error: see javascript console: dlgLoad() cannot load " + url + ", HTTP status: " + xhr.status);
507 reject(xhr.responseURL);
508 }
509 }
510 };
511
512 xhr.open("GET", url, true);
513 xhr.setRequestHeader('Content-type', 'text/html');
514 xhr.send();
515 });
516}
517
518function drawCloseButton(c, mark) {
519 if (!c.getContext)
520 return;
521 let ctx = c.getContext("2d");
522 ctx.clearRect(0, 0, c.width, c.height);
523 ctx.lineWidth = 0.5;
524 ctx.beginPath();
525 ctx.arc(c.width/2, c.height/2, c.width/2-1, 0, 2*Math.PI);
526 ctx.fillStyle = '#FD5E59';
527 ctx.fill();
528 ctx.strokeStyle = '#DF2020';
529 ctx.stroke();
530 if (mark) {
531 ctx.strokeStyle = '#000000';
532 ctx.beginPath();
533 ctx.lineWidth = 1;
534 ctx.moveTo(c.width/2-3, c.height/2-3);
535 ctx.lineTo(c.width/2+3, c.height/2+3);
536 ctx.moveTo(c.width/2+3, c.height/2-3);
537 ctx.lineTo(c.width/2-3, c.height/2+3);
538 ctx.stroke();
539 }
540}
541
542function dlgCenter(dlg) {
543 let d;
544 if (typeof dlg === "string")
545 d = document.getElementById(dlg);
546 else
547 d = dlg;
548
549 if (d === null) {
550 dlgAlert("Dialog '" + dlg + "' does not exist");
551 return;
552 }
553
554 d.style.position = "fixed";
555 d.style.left = Math.round(document.documentElement.clientWidth / 2 - d.offsetWidth / 2) + "px";
556 if (document.documentElement.clientHeight / 2 - d.offsetHeight / 2 < 0)
557 d.style.top = "0px";
558 else
559 d.style.top = Math.round(document.documentElement.clientHeight / 2 - d.offsetHeight / 2) + "px";
560
561 // allow for scrolling of very high dialog boxes
562 if (d.offsetHeight > document.documentElement.clientHeight)
563 d.style.position = "absolute";
564}
565
566function dlgShow(dlg, modal, param) {
567 let d;
568 if (typeof dlg === "string")
569 d = document.getElementById(dlg);
570 else
571 d = dlg;
572
573 if (d === null) {
574 dlgAlert("Dialog '" + dlg + "' does not exist");
575 return;
576 }
577
578 if (d.childNodes === undefined) {
579 dlgAlert("Dialog '" + dlg + "' has no title bar");
580 return;
581 }
582
583 // add optional parameter to dialog
584 d.param = param;
585
586 // put "close" icon into title bar
587 let t;
588 if (d.childNodes[0].className === "dlgTitlebar")
589 t = d.childNodes[0];
590 if (d.childNodes[1] && d.childNodes[1].className === "dlgTitlebar")
591 t = d.childNodes[1];
592 if (t !== undefined) {
593 let ttext = t.innerHTML;
594 if (ttext.search('dlgHide') === -1) {
595 t.innerHTML = "<div style=\"position: absolute;left: 6px;top: 3px;\" " +
596 "onclick=\"dlgClose(this);\">" +
597 "<canvas id=\"cvsClose\" width=\"14px\" height=\"14px\"></canvas>" +
598 "</div>" + ttext;
599 }
600 d.canvas = t.childNodes[0].childNodes[0];
601 drawCloseButton(d.canvas, false);
602 }
603
604 d.dlgAx = 0;
605 d.dlgAy = 0;
606 d.dlgDx = 0;
607 d.dlgDy = 0;
608 d.modal = (modal === true);
609
610 d.style.display = "block";
611 dlgCenter(d);
612
613 // put dialog on top of all other dialogs
614 let dlgs = document.getElementsByClassName("dlgFrame");
615 for (let i = 0; i < dlgs.length; i++)
616 dlgs[i].style.zIndex = "30"; // on top of blackout (20)
617 d.style.zIndex = "31";
618
619 // move dialog right-down if on top of previous one
620 if (dlgs.length > 1) {
621 d.style.left = (parseInt(dlgs[dlgs.length - 2].style.left) + 30).toString() + "px";
622 d.style.top = (parseInt(dlgs[dlgs.length - 2].style.top) + 30).toString() + "px";
623 }
624
625 // enable scrolling if dialog box goes beyond screen
626 d.oldScroll = window.getComputedStyle(document.body).overflow;
627 document.body.style.overflow = "scroll";
628
629 if (d.modal) {
630 let b = document.getElementById("dlgBlackout");
631 if (b === undefined || b === null) {
632 b = document.createElement("div");
633 b.id = "dlgBlackout";
634 b.className = "dlgBlackout";
635 document.body.appendChild(b);
636 }
637
638 b.style.display = "block";
639 d.dlgBlackout = b;
640 }
641
642 d.dlgMouseDown = function (e) {
643 if (d.style.display === "none")
644 return;
645
646 // ignore right mouse clicks
647 if (e.button !== 0)
648 return;
649
650 if ((e.target === this || e.target.parentNode === this) &&
651 e.target.className === "dlgTitlebar") {
652 e.preventDefault();
653 this.Ax = e.clientX;
654 this.Ay = e.clientY;
655 this.Dx = parseInt(this.style.left);
656 this.Dy = parseInt(this.style.top);
657 }
658
659 if (d.modal && e.target !== d && !d.contains(e.target)) {
660 // catch all mouse events outside the dialog
661 e.preventDefault();
662 } else {
663 if (e.target === this || d.contains(e.target)) {
664 let dlgs = document.getElementsByClassName("dlgFrame");
665 for (let i = 0; i < dlgs.length; i++)
666 dlgs[i].style.zIndex = "30";
667 d.style.zIndex = "31";
668 }
669 }
670 };
671
672 d.dlgMouseMove = function (e) {
673 if (d.style.display === "none")
674 return;
675
676 // draw close button with "x" if mouse cursor is inside
677 drawCloseButton(d.canvas, e.target === d.canvas);
678
679 if (this.Ax > 0 && this.Ay > 0) {
680 e.preventDefault();
681 let x = e.clientX;
682 let y = e.clientY;
683 // stop dragging if leaving window
684 if (x < 0 || y < 0 ||
685 x > document.documentElement.clientWidth ||
686 y > document.documentElement.clientHeight ||
687 (this.Dy + (y - this.Ay)) < 0) {
688 this.Ax = 0;
689 this.Ay = 0;
690 } else {
691 this.style.left = (this.Dx + (x - this.Ax)) + "px";
692 this.style.top = (this.Dy + (y - this.Ay)) + "px";
693 }
694 }
695 };
696
697 d.dlgMouseUp = function () {
698 this.Ax = 0;
699 this.Ay = 0;
700 };
701
702 d.dlgTouchStart = function (e) {
703 if (d.style.display === "none")
704 return;
705
706 if ((e.target === this || e.target.parentNode === this) &&
707 e.target.className === "dlgTitlebar") {
708 e.preventDefault();
709 this.Ax = e.targetTouches[0].clientX;
710 this.Ay = e.targetTouches[0].clientY;
711 this.Dx = parseInt(this.style.left);
712 this.Dy = parseInt(this.style.top);
713 }
714
715 if (d.modal && e.target !== d && !d.contains(e.target)) {
716 // catch all mouse events
717 e.preventDefault();
718 } else {
719 if (e.target === this || d.contains(e.target)) {
720 let dlgs = document.getElementsByClassName("dlgFrame");
721 for (let i = 0; i < dlgs.length; i++)
722 dlgs[i].style.zIndex = "30";
723 d.style.zIndex = "31";
724 }
725 }
726 };
727
728 d.dlgTouchMove = function (e) {
729 if (d.style.display === "none")
730 return;
731
732 if (this.Ax > 0 && this.Ay > 0) {
733 e.preventDefault();
734 let x = e.changedTouches[e.changedTouches.length - 1].clientX;
735 let y = e.changedTouches[e.changedTouches.length - 1].clientY;
736 this.style.left = (this.Dx + (x - this.Ax)) + "px";
737 this.style.top = (this.Dy + (y - this.Ay)) + "px";
738 }
739 };
740
741 d.dlgTouchEnd = function (e) {
742 if (d.style.display === "none")
743 return;
744
745 if (this.Ax > 0 && this.Ay > 0) {
746 e.preventDefault();
747 this.Ax = 0;
748 this.Ay = 0;
749 }
750 };
751
752 d.dlgTouchCancel = function (e) {
753 if (d.style.display === "none")
754 return;
755
756 if (this.Ax > 0 && this.Ay > 0) {
757 e.preventDefault();
758 this.Ax = 0;
759 this.Ay = 0;
760 }
761 };
762
763 d.dlgKeyDown = function (e) {
764 if (d.style.display === "none")
765 return;
766
767 if (e.key === 'Escape') {
768 e.preventDefault();
769 dlgClose(d.childNodes[1].childNodes[0]);
770 }
771 }
772 window.addEventListener("keydown", d.dlgKeyDown.bind(d), true);
773
774 window.addEventListener("mousedown", d.dlgMouseDown.bind(d), true);
775 window.addEventListener("mousemove", d.dlgMouseMove.bind(d), true);
776 window.addEventListener("mouseup", d.dlgMouseUp.bind(d), true);
777 window.addEventListener("touchstart", d.dlgTouchStart.bind(d), true);
778 window.addEventListener("touchmove", d.dlgTouchMove.bind(d), true);
779 window.addEventListener("touchend", d.dlgTouchEnd.bind(d), true);
780 window.addEventListener("touchcancel", d.dlgTouchCancel.bind(d), true);
781}
782
783function dlgMove(d, x, y) {
784 d.style.position = "fixed";
785 d.style.left = x + "px";
786 d.style.top = y + "px";
787}
788
789function dlgClose(div) {
790 let dlg = div;
791 while (!dlg.className.includes("dlgFrame"))
792 dlg = dlg.parentElement;
793 if (dlg.shouldDestroy)
794 dlgMessageDestroy(dlg);
795 else if (dlg.id !== undefined)
796 dlgHide(dlg.id);
797}
798
799function dlgHide(dlg) {
800 if (typeof dlg === "string")
801 dlg = document.getElementById(dlg);
802 else if (dlg.type === "button") {
803 do {
804 dlg = dlg.parentElement;
805 } while (dlg.className !== 'dlgFrame');
806 }
807
808 if (dlg.modal) {
809 // only remove blackout if we are the only modal dialog left
810 let dlgs = document.getElementsByClassName("dlgFrame");
811 let n=0;
812 for (let i = 0; i < dlgs.length; i++)
813 if (dlgs[i].style.display === "block" && dlgs[i].modal)
814 n++;
815 if (n === 1) {
816 let d = document.getElementById("dlgBlackout");
817 if (d !== undefined && d !== null)
818 d.style.display = "none";
819 }
820 }
821 dlg.style.display = "none";
822 if (dlg.oldScroll !== "")
823 document.body.style.overflow = dlg.oldScroll;
824}
825
826function dlgMessageDestroy(b) {
827 let dlg = b;
828 while (!dlg.className.includes("dlgFrame"))
829 dlg = dlg.parentElement;
830 if (dlg.modal) {
831 // only remove blackout if we are the only modal dialog left
832 let dlgs = document.getElementsByClassName("dlgFrame");
833 let n=0;
834 for (let i = 0; i < dlgs.length; i++)
835 if (dlgs[i].style.display === "block" && dlgs[i].modal)
836 n++;
837 if (n === 1) {
838 let d = document.getElementById("dlgBlackout");
839 if (d !== undefined && d !== null)
840 d.style.display = "none";
841 }
842 }
843 // dialog is not really removed from memory, event listerner is still active and
844 // grabs mousdown events, so mark its display "none" to prevent eating mouse events
845 // above in dlgMouseDown routine
846 dlg.style.display = "none";
847 document.body.removeChild(dlg);
848}
849
850function dlgMessage(title, string, modal, error, callback, param) {
851 let d = document.createElement("div");
852 d.className = "dlgFrame";
853 d.style.zIndex = modal ? "31" : "30";
854 d.callback = callback;
855 d.callbackParam = param;
856 d.shouldDestroy = true;
857
858 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">" + title + "</div>" +
859 "<div class=\"dlgPanel\" style=\"padding: 30px;\">" +
860 "<div id=\"dlgMessageString\">" + string + "</div>" +
861 "<br /><br /><button class=\"dlgButton\" id=\"dlgMessageButton\" type=\"button\" " +
862 " onClick=\"let d=this.parentElement.parentElement;if(d.callback!==undefined)d.callback(d.callbackParam);dlgMessageDestroy(this)\">Close</button>" +
863 "</div>";
864
865 document.body.appendChild(d);
866
867 if (error === true) {
868 let t = document.getElementById("dlgMessageTitle");
869 t.style.backgroundColor = "#9E2A2A";
870 t.style.color = "white";
871 }
872
873 dlgShow(d, modal);
874 return d;
875}
876
877function dlgAlert(s, callback) {
878 dlgMessage('Message', s, true, false, callback);
879}
880
881function dlgConfirm(string, confirmCallback, param) {
882 let d = document.createElement("div");
883 d.className = "dlgFrame";
884 d.style.zIndex = "31";
885 d.callback = confirmCallback;
886 d.callbackParam = param;
887 d.shouldDestroy = true;
888
889 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">Please confirm</div>" +
890 "<div class=\"dlgPanel\" style=\"padding: 30px;\">" +
891 "<div id=\"dlgMessageString\">" + string + "</div>" +
892 "<br /><br />" +
893 "<button class=\"dlgButtonDefault\" id=\"dlgMessageButtonOk\" type=\"button\" " +
894 " onClick=\"let d=this.parentElement.parentElement;d.callback(true,d.callbackParam);dlgMessageDestroy(this);\">OK</button>" +
895 "<button class=\"dlgButton\" id=\"dlgMessageButtonCancel\" type=\"button\" " +
896 " onClick=\"let d=this.parentElement.parentElement;d.callback(false,d.callbackParam);dlgMessageDestroy(this);\">Cancel</button>" +
897 "</div>";
898
899 document.body.appendChild(d);
900
901 dlgShow(d, true);
902 document.getElementById('dlgMessageButtonOk').focus();
903 return d;
904}
905
906// Populate a modal with a general html
907function dlgGeneral(p) {
908 /* general dialog containing p.html code and other optional parameters
909 p.iddiv - the name of the dialog div (optional)
910 p.width/p.height - minimal width/height of dialog (optional)
911 p.minWidth/p.minHeight - minimal width/height of dialog (optional)
912 p.x/p.y - initial position of dialog (optional)
913 p.title - title of the dialog (optional)
914 */
915
916 // First make sure you removed existing iddiv
917 if (document.getElementById(p.iddiv)) document.getElementById(p.iddiv).remove();
918 let d = document.createElement("div");
919 d.className = "dlgFrame";
920 if (p.iddiv) d.id = p.iddiv;
921 d.style.zIndex = "30";
922 d.style.overflow = "hidden";
923 d.style.resize = "both";
924 d.style.width = p.width? p.width + "px" : "400px";
925 d.style.height = p.height ? p.height + "px" : "200px";
926 d.style.minWidth = p.minWidth ? p.minWidth + "px" : d.style.width;
927 d.style.minHeight = p.minHeight ? p.minHeight + "px" : d.style.height;
928 d.style.maxHeight = "90vh";
929 d.style.maxWidth = "60vw";
930 d.shouldDestroy = true;
931
932 const dlgTitle = document.createElement("div");
933 dlgTitle.className = "dlgTitlebar";
934 //dlgTitle.id = "dlgMessageTitle";
935 dlgTitle.innerText = p.title || (p.iddiv ? p.iddiv + " dialog" : "General dialog");
936 d.appendChild(dlgTitle);
937
938 const dlgPanel = document.createElement("div");
939 dlgPanel.className = "dlgPanel";
940 //dlgPanel.id = "dlgPanel";
941 d.appendChild(dlgPanel);
942
943 const content = document.createElement("div");
944 //content.id = "dlgHTML";
945 content.style.overflow = "auto";
946 content.innerHTML = p.html;
947 dlgPanel.appendChild(content);
948
949 document.body.appendChild(d);
950 dlgShow(d);
951
952 if (p.x !== undefined && p.y !== undefined)
953 dlgMove(d, p.x, p.y);
954
955 // Initial size based on content
956 d.style.height = (content.scrollHeight + dlgTitle.offsetHeight + 5 ) + "px";
957 d.style.width = (content.scrollWidth + 5 ) + "px";
958
959 // Function to handle resize events
960 function handleResize() {
961 content.style.height = (d.offsetHeight - dlgTitle.offsetHeight - 5 ) + "px";
962 }
963
964 // Resize observer to watch for dialog resizing
965 const resizeObs = new ResizeObserver(handleResize);
966 resizeObs.observe(d);
967
968 return d;
969}
970
971function dlgQueryKeyDown(event, inp) {
972 let keyCode = ('which' in event) ? event.which : event.keyCode;
973
974 if (keyCode === 27) {
975 // cancel editing
976 let d = inp.parentElement.parentElement.parentElement;
977 d.callback(false, d.callbackParam);
978 dlgMessageDestroy(inp.parentElement);
979 return false;
980 }
981
982 if (keyCode === 13) {
983 // finish editing
984 let d = inp.parentElement.parentElement.parentElement;
985 d.callback(inp.value, d.callbackParam);
986 dlgMessageDestroy(inp.parentElement);
987 return false;
988 }
989
990 return true;
991}
992
993function dlgQuery(string, value, queryCallback, param, size) {
994 let d = document.createElement("div");
995 d.className = "dlgFrame";
996 d.style.zIndex = "31";
997 d.callback = queryCallback;
998 d.callbackParam = param;
999 d.shouldDestroy = true;
1000
1001 if (size === undefined)
1002 size = 30;
1003
1004 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">&nbsp;</div>" +
1005 "<div class=\"dlgPanel\" style=\"padding: 20px;\">" +
1006 "<div id=\"dlgMessageString\">" + string + "&nbsp;&nbsp;<input type='text' size='"+size+"' id='dlgQueryInput' onkeydown='return dlgQueryKeyDown(event, this);' value='" + value + "'></input></div>" +
1007 "<br /><br />" +
1008 "<button class=\"dlgButtonDefault\" id=\"dlgMessageButton\" type=\"button\" " +
1009 " onClick=\"let d=this.parentElement.parentElement;d.callback(document.getElementById('dlgQueryInput').value,d.callbackParam);dlgMessageDestroy(this);\">OK</button>" +
1010 "<button class=\"dlgButton\" id=\"dlgMessageButton\" type=\"button\" " +
1011 " onClick=\"let d=this.parentElement.parentElement;d.callback(false,d.callbackParam);dlgMessageDestroy(this);\">Cancel</button>" +
1012 "</div>";
1013
1014 document.body.appendChild(d);
1015
1016 dlgShow(d, true);
1017 document.getElementById('dlgQueryInput').focus();
1018 document.getElementById('dlgQueryInput').select();
1019
1020 return d;
1021}
1022
1023let dlgWaitDialog;
1024let dlgWaitProgress;
1025let dlgWaitTime;
1026let dlgWaitFunc;
1027let dlgWaitFuncParam;
1028
1029function dlgWait(time, string, func, param) {
1030
1031 let d = document.createElement("div");
1032 d.className = "dlgFrame";
1033 d.style.zIndex = "31";
1034 d.shouldDestroy = true;
1035
1036 <!-- wait dialog -->
1037 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">Please wait...</div>" +
1038 "<div class=\"dlgPanel\" style=\"padding: 20px;\">" +
1039 "<div id=\"dlgMessageString\">" + string + "</div>" +
1040 "<br />" +
1041 "<div name=\"ctrlProgress\" style=\"width:250px;\" id=\"dlgWaitProgress\"></div>" +
1042 "</div>";
1043
1044 document.body.appendChild(d);
1045
1046 // init progress bar
1047 let p = document.getElementById('dlgWaitProgress');
1048 p.className = "ctrlProgress";
1049 let ind = document.createElement("div");
1050 ind.className = "ctrlProgressInd";
1051 ind.style.height = p.style.height;
1052 ind.style.backgroundColor = p.style.color;
1053 p.appendChild(ind);
1054 p.set = function (value) {
1055 this.firstChild.style.width = Math.round(parseInt(this.style.width) * value) + "px";
1056 };
1057
1058 dlgShow(d, true);
1059 dlgWaitDialog = d;
1060
1061 dlgWaitProgress = 0;
1062 dlgWaitTime = time;
1063 dlgWaitFunc = func;
1064 dlgWaitFuncParam = param;
1065 window.setTimeout(updateDlgWaitProgress, 100);
1066}
1067
1068function updateDlgWaitProgress() {
1069 dlgWaitProgress += 0.1;
1070 let d = document.getElementById("dlgWaitProgress");
1071 d.set(dlgWaitProgress / dlgWaitTime);
1072 if (dlgWaitProgress >= dlgWaitTime) {
1073 dlgWaitDialog.style.display = "none";
1074 document.body.removeChild(dlgWaitDialog);
1075 let d = document.getElementById("dlgBlackout");
1076 if (d !== undefined && d !== null)
1077 d.style.display = "none";
1078 if (dlgWaitFunc !== undefined)
1079 dlgWaitFunc(dlgWaitFuncParam);
1080 } else
1081 window.setTimeout(updateDlgWaitProgress, 100);
1082}