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.left = Math.round(document.documentElement.clientWidth / 2 - d.offsetWidth / 2) + "px";
555 if (document.documentElement.clientHeight / 2 - d.offsetHeight / 2 < 0)
556 d.style.top = "0px";
557 else
558 d.style.top = Math.round(document.documentElement.clientHeight / 2 - d.offsetHeight / 2) + "px";
559
560 // allow for scrolling of very high dialog boxes
561 if (d.offsetHeight > document.documentElement.clientHeight)
562 d.style.position = "absolute";
563}
564
565function dlgShow(dlg, modal, param) {
566 let d;
567 if (typeof dlg === "string")
568 d = document.getElementById(dlg);
569 else
570 d = dlg;
571
572 if (d === null) {
573 dlgAlert("Dialog '" + dlg + "' does not exist");
574 return;
575 }
576
577 if (d.childNodes === undefined) {
578 dlgAlert("Dialog '" + dlg + "' has no title bar");
579 return;
580 }
581
582 // add optional parameter to dialog
583 d.param = param;
584
585 // put "close" icon into title bar
586 let t;
587 if (d.childNodes[0].className === "dlgTitlebar")
588 t = d.childNodes[0];
589 if (d.childNodes[1] && d.childNodes[1].className === "dlgTitlebar")
590 t = d.childNodes[1];
591 if (t !== undefined) {
592 let ttext = t.innerHTML;
593 if (ttext.search('dlgHide') === -1) {
594 t.innerHTML = "<div style=\"position: absolute;left: 6px;top: 3px;\" " +
595 "onclick=\"dlgClose(this);\">" +
596 "<canvas id=\"cvsClose\" width=\"14px\" height=\"14px\"></canvas>" +
597 "</div>" + ttext;
598 }
599 d.canvas = t.childNodes[0].childNodes[0];
600 drawCloseButton(d.canvas, false);
601 }
602
603 d.dlgAx = 0;
604 d.dlgAy = 0;
605 d.dlgDx = 0;
606 d.dlgDy = 0;
607 d.modal = (modal === true);
608
609 d.style.display = "block";
610 dlgCenter(d);
611
612 // put dialog on top of all other dialogs
613 let dlgs = document.getElementsByClassName("dlgFrame");
614 for (let i = 0; i < dlgs.length; i++)
615 dlgs[i].style.zIndex = "30"; // on top of blackout (20)
616 d.style.zIndex = "31";
617
618 // move dialog right-down if on top of previous one
619 if (dlgs.length > 1) {
620 d.style.left = (parseInt(dlgs[dlgs.length - 2].style.left) + 30).toString() + "px";
621 d.style.top = (parseInt(dlgs[dlgs.length - 2].style.top) + 30).toString() + "px";
622 }
623
624 // enable scrolling if dialog box goes beyond screen
625 d.oldScroll = window.getComputedStyle(document.body).overflow;
626 document.body.style.overflow = "scroll";
627
628 if (d.modal) {
629 let b = document.getElementById("dlgBlackout");
630 if (b === undefined || b === null) {
631 b = document.createElement("div");
632 b.id = "dlgBlackout";
633 b.className = "dlgBlackout";
634 document.body.appendChild(b);
635 }
636
637 b.style.display = "block";
638 d.dlgBlackout = b;
639 }
640
641 d.dlgMouseDown = function (e) {
642 if (d.style.display === "none")
643 return;
644
645 // ignore right mouse clicks
646 if (e.button !== 0)
647 return;
648
649 if ((e.target === this || e.target.parentNode === this) &&
650 e.target.className === "dlgTitlebar") {
651 e.preventDefault();
652 this.Ax = e.clientX;
653 this.Ay = e.clientY;
654 this.Dx = parseInt(this.style.left);
655 this.Dy = parseInt(this.style.top);
656 }
657
658 if (d.modal && e.target !== d && !d.contains(e.target)) {
659 // catch all mouse events outside the dialog
660 e.preventDefault();
661 } else {
662 if (e.target === this || d.contains(e.target)) {
663 let dlgs = document.getElementsByClassName("dlgFrame");
664 for (let i = 0; i < dlgs.length; i++)
665 dlgs[i].style.zIndex = "30";
666 d.style.zIndex = "31";
667 }
668 }
669 };
670
671 d.dlgMouseMove = function (e) {
672 if (d.style.display === "none")
673 return;
674
675 // draw close button with "x" if mouse cursor is inside
676 drawCloseButton(d.canvas, e.target === d.canvas);
677
678 if (this.Ax > 0 && this.Ay > 0) {
679 e.preventDefault();
680 let x = e.clientX;
681 let y = e.clientY;
682 // stop dragging if leaving window
683 if (x < 0 || y < 0 ||
684 x > document.documentElement.clientWidth ||
685 y > document.documentElement.clientHeight ||
686 (this.Dy + (y - this.Ay)) < 0) {
687 this.Ax = 0;
688 this.Ay = 0;
689 } else {
690 this.style.left = (this.Dx + (x - this.Ax)) + "px";
691 this.style.top = (this.Dy + (y - this.Ay)) + "px";
692 }
693 }
694 };
695
696 d.dlgMouseUp = function () {
697 this.Ax = 0;
698 this.Ay = 0;
699 };
700
701 d.dlgTouchStart = function (e) {
702 if (d.style.display === "none")
703 return;
704
705 if ((e.target === this || e.target.parentNode === this) &&
706 e.target.className === "dlgTitlebar") {
707 e.preventDefault();
708 this.Ax = e.targetTouches[0].clientX;
709 this.Ay = e.targetTouches[0].clientY;
710 this.Dx = parseInt(this.style.left);
711 this.Dy = parseInt(this.style.top);
712 }
713
714 if (d.modal && e.target !== d && !d.contains(e.target)) {
715 // catch all mouse events
716 e.preventDefault();
717 } else {
718 if (e.target === this || d.contains(e.target)) {
719 let dlgs = document.getElementsByClassName("dlgFrame");
720 for (let i = 0; i < dlgs.length; i++)
721 dlgs[i].style.zIndex = "30";
722 d.style.zIndex = "31";
723 }
724 }
725 };
726
727 d.dlgTouchMove = function (e) {
728 if (d.style.display === "none")
729 return;
730
731 if (this.Ax > 0 && this.Ay > 0) {
732 e.preventDefault();
733 let x = e.changedTouches[e.changedTouches.length - 1].clientX;
734 let y = e.changedTouches[e.changedTouches.length - 1].clientY;
735 this.style.left = (this.Dx + (x - this.Ax)) + "px";
736 this.style.top = (this.Dy + (y - this.Ay)) + "px";
737 }
738 };
739
740 d.dlgTouchEnd = function (e) {
741 if (d.style.display === "none")
742 return;
743
744 if (this.Ax > 0 && this.Ay > 0) {
745 e.preventDefault();
746 this.Ax = 0;
747 this.Ay = 0;
748 }
749 };
750
751 d.dlgTouchCancel = function (e) {
752 if (d.style.display === "none")
753 return;
754
755 if (this.Ax > 0 && this.Ay > 0) {
756 e.preventDefault();
757 this.Ax = 0;
758 this.Ay = 0;
759 }
760 };
761
762 d.dlgKeyDown = function (e) {
763 if (d.style.display === "none")
764 return;
765
766 if (e.key === 'Escape') {
767 e.preventDefault();
768 dlgClose(d.childNodes[1].childNodes[0]);
769 }
770 }
771 window.addEventListener("keydown", d.dlgKeyDown.bind(d), true);
772
773 window.addEventListener("mousedown", d.dlgMouseDown.bind(d), true);
774 window.addEventListener("mousemove", d.dlgMouseMove.bind(d), true);
775 window.addEventListener("mouseup", d.dlgMouseUp.bind(d), true);
776 window.addEventListener("touchstart", d.dlgTouchStart.bind(d), true);
777 window.addEventListener("touchmove", d.dlgTouchMove.bind(d), true);
778 window.addEventListener("touchend", d.dlgTouchEnd.bind(d), true);
779 window.addEventListener("touchcancel", d.dlgTouchCancel.bind(d), true);
780}
781
782function dlgMove(d, x, y) {
783 d.style.left = x + "px";
784 d.style.top = y + "px";
785}
786
787function dlgClose(div) {
788 let dlg = div;
789 while (!dlg.className.includes("dlgFrame"))
790 dlg = dlg.parentElement;
791 if (dlg.shouldDestroy)
792 dlgMessageDestroy(dlg);
793 else if (dlg.id !== undefined)
794 dlgHide(dlg.id);
795}
796
797function dlgHide(dlg) {
798 if (typeof dlg === "string")
799 dlg = document.getElementById(dlg);
800 else if (dlg.type === "button") {
801 do {
802 dlg = dlg.parentElement;
803 } while (dlg.className !== 'dlgFrame');
804 }
805
806 if (dlg.modal) {
807 // only remove blackout if we are the only modal dialog left
808 let dlgs = document.getElementsByClassName("dlgFrame");
809 let n=0;
810 for (let i = 0; i < dlgs.length; i++)
811 if (dlgs[i].style.display === "block" && dlgs[i].modal)
812 n++;
813 if (n === 1) {
814 let d = document.getElementById("dlgBlackout");
815 if (d !== undefined && d !== null)
816 d.style.display = "none";
817 }
818 }
819 dlg.style.display = "none";
820 if (dlg.oldScroll !== "")
821 document.body.style.overflow = dlg.oldScroll;
822}
823
824function dlgMessageDestroy(b) {
825 let dlg = b;
826 while (!dlg.className.includes("dlgFrame"))
827 dlg = dlg.parentElement;
828 if (dlg.modal) {
829 // only remove blackout if we are the only modal dialog left
830 let dlgs = document.getElementsByClassName("dlgFrame");
831 let n=0;
832 for (let i = 0; i < dlgs.length; i++)
833 if (dlgs[i].style.display === "block" && dlgs[i].modal)
834 n++;
835 if (n === 1) {
836 let d = document.getElementById("dlgBlackout");
837 if (d !== undefined && d !== null)
838 d.style.display = "none";
839 }
840 }
841 // dialog is not really removed from memory, event listerner is still active and
842 // grabs mousdown events, so mark its display "none" to prevent eating mouse events
843 // above in dlgMouseDown routine
844 dlg.style.display = "none";
845 document.body.removeChild(dlg);
846}
847
848function dlgMessage(title, string, modal, error, callback, param) {
849 let d = document.createElement("div");
850 d.className = "dlgFrame";
851 d.style.zIndex = modal ? "31" : "30";
852 d.callback = callback;
853 d.callbackParam = param;
854 d.shouldDestroy = true;
855
856 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">" + title + "</div>" +
857 "<div class=\"dlgPanel\" style=\"padding: 30px;\">" +
858 "<div id=\"dlgMessageString\">" + string + "</div>" +
859 "<br /><br /><button class=\"dlgButton\" id=\"dlgMessageButton\" type=\"button\" " +
860 " onClick=\"let d=this.parentElement.parentElement;if(d.callback!==undefined)d.callback(d.callbackParam);dlgMessageDestroy(this)\">Close</button>" +
861 "</div>";
862
863 document.body.appendChild(d);
864
865 if (error === true) {
866 let t = document.getElementById("dlgMessageTitle");
867 t.style.backgroundColor = "#9E2A2A";
868 t.style.color = "white";
869 }
870
871 dlgShow(d, modal);
872 return d;
873}
874
875function dlgAlert(s, callback) {
876 dlgMessage('Message', s, true, false, callback);
877}
878
879function dlgConfirm(string, confirmCallback, param) {
880 let d = document.createElement("div");
881 d.className = "dlgFrame";
882 d.style.zIndex = "31";
883 d.callback = confirmCallback;
884 d.callbackParam = param;
885 d.shouldDestroy = true;
886
887 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">Please confirm</div>" +
888 "<div class=\"dlgPanel\" style=\"padding: 30px;\">" +
889 "<div id=\"dlgMessageString\">" + string + "</div>" +
890 "<br /><br />" +
891 "<button class=\"dlgButtonDefault\" id=\"dlgMessageButtonOk\" type=\"button\" " +
892 " onClick=\"let d=this.parentElement.parentElement;d.callback(true,d.callbackParam);dlgMessageDestroy(this);\">OK</button>" +
893 "<button class=\"dlgButton\" id=\"dlgMessageButtonCancel\" type=\"button\" " +
894 " onClick=\"let d=this.parentElement.parentElement;d.callback(false,d.callbackParam);dlgMessageDestroy(this);\">Cancel</button>" +
895 "</div>";
896
897 document.body.appendChild(d);
898
899 dlgShow(d, true);
900 document.getElementById('dlgMessageButtonOk').focus();
901 return d;
902}
903
904// Populate a modal with a general html
905function dlgGeneral(p) {
906 /* general dialog containing p.html code and other optional parameters
907 p.iddiv - the name of the dialog div (optional)
908 p.width/p.height - minimal width/height of dialog (optional)
909 p.minWidth/p.minHeight - minimal width/height of dialog (optional)
910 p.x/p.y - initial position of dialog (optional)
911 p.title - title of the dialog (optional)
912 */
913
914 // First make sure you removed existing iddiv
915 if (document.getElementById(p.iddiv)) document.getElementById(p.iddiv).remove();
916 let d = document.createElement("div");
917 d.className = "dlgFrame";
918 if (p.iddiv) d.id = p.iddiv;
919 d.style.zIndex = "30";
920 d.style.overflow = "hidden";
921 d.style.resize = "both";
922 d.style.width = p.width? p.width + "px" : "400px";
923 d.style.height = p.height ? p.height + "px" : "200px";
924 d.style.minWidth = p.minWidth ? p.minWidth + "px" : d.style.width;
925 d.style.minHeight = p.minHeight ? p.minHeight + "px" : d.style.height;
926 d.style.maxHeight = "90vh";
927 d.style.maxWidth = "60vw";
928 d.shouldDestroy = true;
929
930 const dlgTitle = document.createElement("div");
931 dlgTitle.className = "dlgTitlebar";
932 //dlgTitle.id = "dlgMessageTitle";
933 dlgTitle.innerText = p.title || (p.iddiv ? p.iddiv + " dialog" : "General dialog");
934 d.appendChild(dlgTitle);
935
936 const dlgPanel = document.createElement("div");
937 dlgPanel.className = "dlgPanel";
938 //dlgPanel.id = "dlgPanel";
939 d.appendChild(dlgPanel);
940
941 const content = document.createElement("div");
942 //content.id = "dlgHTML";
943 content.style.overflow = "auto";
944 content.innerHTML = p.html;
945 dlgPanel.appendChild(content);
946
947 document.body.appendChild(d);
948 dlgShow(d);
949
950 if (p.x !== undefined && p.y !== undefined)
951 dlgMove(d, p.x, p.y);
952
953 // Initial size based on content
954 d.style.height = (content.scrollHeight + dlgTitle.offsetHeight + 5 ) + "px";
955 d.style.width = (content.scrollWidth + 5 ) + "px";
956
957 // Function to handle resize events
958 function handleResize() {
959 content.style.height = (d.offsetHeight - dlgTitle.offsetHeight - 5 ) + "px";
960 }
961
962 // Resize observer to watch for dialog resizing
963 const resizeObs = new ResizeObserver(handleResize);
964 resizeObs.observe(d);
965
966 return d;
967}
968
969function dlgQueryKeyDown(event, inp) {
970 let keyCode = ('which' in event) ? event.which : event.keyCode;
971
972 if (keyCode === 27) {
973 // cancel editing
974 let d = inp.parentElement.parentElement.parentElement;
975 d.callback(false, d.callbackParam);
976 dlgMessageDestroy(inp.parentElement);
977 return false;
978 }
979
980 if (keyCode === 13) {
981 // finish editing
982 let d = inp.parentElement.parentElement.parentElement;
983 d.callback(inp.value, d.callbackParam);
984 dlgMessageDestroy(inp.parentElement);
985 return false;
986 }
987
988 return true;
989}
990
991function dlgQuery(string, value, queryCallback, param, size) {
992 let d = document.createElement("div");
993 d.className = "dlgFrame";
994 d.style.zIndex = "31";
995 d.callback = queryCallback;
996 d.callbackParam = param;
997 d.shouldDestroy = true;
998
999 if (size === undefined)
1000 size = 30;
1001
1002 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">&nbsp;</div>" +
1003 "<div class=\"dlgPanel\" style=\"padding: 20px;\">" +
1004 "<div id=\"dlgMessageString\">" + string + "&nbsp;&nbsp;<input type='text' size='"+size+"' id='dlgQueryInput' onkeydown='return dlgQueryKeyDown(event, this);' value='" + value + "'></input></div>" +
1005 "<br /><br />" +
1006 "<button class=\"dlgButtonDefault\" id=\"dlgMessageButton\" type=\"button\" " +
1007 " onClick=\"let d=this.parentElement.parentElement;d.callback(document.getElementById('dlgQueryInput').value,d.callbackParam);dlgMessageDestroy(this);\">OK</button>" +
1008 "<button class=\"dlgButton\" id=\"dlgMessageButton\" type=\"button\" " +
1009 " onClick=\"let d=this.parentElement.parentElement;d.callback(false,d.callbackParam);dlgMessageDestroy(this);\">Cancel</button>" +
1010 "</div>";
1011
1012 document.body.appendChild(d);
1013
1014 dlgShow(d, true);
1015 document.getElementById('dlgQueryInput').focus();
1016 document.getElementById('dlgQueryInput').select();
1017
1018 return d;
1019}
1020
1021let dlgWaitDialog;
1022let dlgWaitProgress;
1023let dlgWaitTime;
1024let dlgWaitFunc;
1025let dlgWaitFuncParam;
1026
1027function dlgWait(time, string, func, param) {
1028
1029 let d = document.createElement("div");
1030 d.className = "dlgFrame";
1031 d.style.zIndex = "31";
1032 d.shouldDestroy = true;
1033
1034 <!-- wait dialog -->
1035 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">Please wait...</div>" +
1036 "<div class=\"dlgPanel\" style=\"padding: 20px;\">" +
1037 "<div id=\"dlgMessageString\">" + string + "</div>" +
1038 "<br />" +
1039 "<div name=\"ctrlProgress\" style=\"width:250px;\" id=\"dlgWaitProgress\"></div>" +
1040 "</div>";
1041
1042 document.body.appendChild(d);
1043
1044 // init progress bar
1045 let p = document.getElementById('dlgWaitProgress');
1046 p.className = "ctrlProgress";
1047 let ind = document.createElement("div");
1048 ind.className = "ctrlProgressInd";
1049 ind.style.height = p.style.height;
1050 ind.style.backgroundColor = p.style.color;
1051 p.appendChild(ind);
1052 p.set = function (value) {
1053 this.firstChild.style.width = Math.round(parseInt(this.style.width) * value) + "px";
1054 };
1055
1056 dlgShow(d, true);
1057 dlgWaitDialog = d;
1058
1059 dlgWaitProgress = 0;
1060 dlgWaitTime = time;
1061 dlgWaitFunc = func;
1062 dlgWaitFuncParam = param;
1063 window.setTimeout(updateDlgWaitProgress, 100);
1064}
1065
1066function updateDlgWaitProgress() {
1067 dlgWaitProgress += 0.1;
1068 let d = document.getElementById("dlgWaitProgress");
1069 d.set(dlgWaitProgress / dlgWaitTime);
1070 if (dlgWaitProgress >= dlgWaitTime) {
1071 dlgWaitDialog.style.display = "none";
1072 document.body.removeChild(dlgWaitDialog);
1073 let d = document.getElementById("dlgBlackout");
1074 if (d !== undefined && d !== null)
1075 d.style.display = "none";
1076 if (dlgWaitFunc !== undefined)
1077 dlgWaitFunc(dlgWaitFuncParam);
1078 } else
1079 window.setTimeout(updateDlgWaitProgress, 100);
1080}