5// Created by Stefan Ritt on 5/8/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.
22 Following button shows the dialog dlgXXX:
24 <button type="button" onClick="dlgShow('dlgXXX')">XXX</button>
26 Following HTML code defines the dialog dlgXXX:
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>
41 Replacement for alert() function showing a standard notification
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().
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
59 function cb(value, param) {
61 alert('Value is '+value+', param is '+param);
64 where 'param' can also be ommitted.
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.
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.
80 <button name="ctrlHSlider" type="button" class="ctrlHSlider" data-update="xxx()" id="yyy"></button>
82 <button name="ctrlVSlider" type="button" class="ctrlVSlider" data-update="xxx()" id="yyy"></button>
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 function is called
87 once with final = true.
89 To set the slider programmatically call
91 document.getElementById('yyy').set(0.5);
93 Valid range is again 0 to 1.
99 <div name="ctrlProgress" style="width:xxxpx;color:yyy;" id="zzz"></div>
101 document.getElementById('yyy').set(v); // v = 0...1
108 <button name="ctrlButton" data-draw="xxx" type="button" id="yyy" onClick="zzz()"></button>
112 // example for an up-arrow with 36x36 pixels
115 let ctx = cvs.getContext("2d");
116 ctx.fillStyle = "#E0E0E0";
117 ctx.fillRect(0, 0, 36, 36);
124 ctx.fillStyle = "#808080";
130// default styles for dialog boxes
131let controls_css = `<style>
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);
141 display: none; /* pre-hidden, to be changed to flex */
142 flex-direction: column; /* needed for dlgPanel to follow our size */
143 min-width: max-content;
144 min-height: max-content;
149 background-color: #C0C0C0;
150 border-top-left-radius: 6px;
151 border-top-right-radius: 6px;
159 white-space: nowrap;*/
166 background-color: #F8F8F8;
167 border: 1px solid #808080;
174 background-color: #4187F7;
176 border: 1px solid #808080;
182 background-color: #F0F0F0;
185 background-color: #F0F0F0;
188 border-bottom-left-radius: 6px;
189 border-bottom-right-radius: 6px;
190 flex: 1 1 auto; /* follow dlgFrame in size */
191 min-width: max-content;
192 min-height: max-content;
195 background: rgba(0,0,0,.5);
216 border: 1px solid #A0A0A0;
220 background-color: #E0E0E0;
228 background-color: #419bf9;
233(function (window) { // anonymous global function
234 window.addEventListener("load", ctlInit, false);
238 let CTL = new Controls();
242function Controls() // constructor
246Controls.prototype.init = function () // scan DOM
249 document.head.insertAdjacentHTML("beforeend", controls_css);
251 // scan DOM for controls
252 this.ctrlButton = document.getElementsByName("ctrlButton");
253 this.ctrlVSlider = document.getElementsByName("ctrlVSlider");
254 this.ctrlHSlider = document.getElementsByName("ctrlHSlider");
255 this.ctrlProgress = document.getElementsByName("ctrlProgress");
258 for (let i = 0; i < this.ctrlButton.length; i++) {
259 let cvs = document.createElement("canvas");
260 this.ctrlButton[i].appendChild(cvs);
262 if (this.ctrlButton[i].dataset.draw !== undefined)
263 eval(this.ctrlButton[i].dataset.draw + "(cvs)");
267 for (let i = 0; i < this.ctrlVSlider.length; i++) {
268 let cvs = document.createElement("canvas");
269 let sl = this.ctrlVSlider[i];
271 if (sl.style.height === "") {
272 dlgAlert("ctrlVSlider: undefined height");
275 if (sl.dataset.minValue === undefined)
278 sl.minValue = Number.parseFloat(sl.dataset.minValue);
279 if (sl.dataset.maxValue === undefined)
282 sl.maxValue = Number.parseFloat(sl.dataset.maxValue);
285 cvs.height = Number.parseInt(sl.style.height)-4;
289 sl.value = (sl.minValue + sl.maxValue) / 2;
290 sl.position = 0.5; // slider position 0...1
291 sl.addEventListener("click", this.ctrlVSliderHandler.bind(this));
292 sl.addEventListener("contextmenu", this.ctrlVSliderHandler.bind(this));
293 sl.addEventListener("mousemove", this.ctrlVSliderHandler.bind(this));
294 sl.addEventListener("touchmove", this.ctrlVSliderHandler.bind(this));
295 sl.draw = this.ctrlVSliderDraw;
297 sl.set = this.ctrlVSliderSet;
301 for (let i = 0; i < this.ctrlHSlider.length; i++) {
302 let cvs = document.createElement("canvas");
303 let sl = this.ctrlHSlider[i];
305 if (sl.style.width === "") {
306 dlgAlert("ctrlHSlider: undefined width");
309 if (sl.dataset.minValue === undefined)
312 sl.minValue = Number.parseFloat(sl.dataset.minValue);
313 if (sl.dataset.maxValue === undefined)
316 sl.maxValue = Number.parseFloat(sl.dataset.maxValue);
318 cvs.width = Number.parseInt(sl.style.width)-4;
324 sl.value = (sl.minValue + sl.maxValue) / 2;
325 sl.position = 0.5; // slider position 0...1
326 sl.addEventListener("click", this.ctrlHSliderHandler.bind(this));
327 sl.addEventListener("contextmenu", this.ctrlHSliderHandler.bind(this));
328 sl.addEventListener("mousemove", this.ctrlHSliderHandler.bind(this));
329 sl.addEventListener("touchmove", this.ctrlHSliderHandler.bind(this));
330 sl.addEventListener("mouseup", this.ctrlHSliderHandler.bind(this));
331 sl.draw = this.ctrlHSliderDraw;
333 sl.set = this.ctrlHSliderSet;
337 for (let i = 0; i < this.ctrlProgress.length; i++) {
338 let p = this.ctrlProgress[i];
339 p.className = "ctrlProgress";
340 let ind = document.createElement("div");
341 ind.className = "ctrlProgressInd";
342 ind.style.height = p.style.height;
343 ind.style.backgroundColor = p.style.color;
345 p.set = this.ctrlProgressSet;
350Controls.prototype.ctrlVSliderDraw = function (b) {
353 let w = b.canvas.width;
354 let h = b.canvas.height;
357 let ctx = b.canvas.getContext("2d");
358 ctx.fillStyle = "#E0E0E0";
359 ctx.fillRect(0, 0, b.canvas.width, b.canvas.height);
361 let knob = b.sliderOfs + (1 - b.position) * (h - 2 * b.sliderOfs);
363 ctx.strokeStyle = "#A0A0A0";
366 ctx.moveTo(w / 2, b.sliderOfs);
367 ctx.lineTo(w / 2, knob);
370 ctx.strokeStyle = "#00A0FF";
372 ctx.moveTo(w / 2, knob);
373 ctx.lineTo(w / 2, h - b.sliderOfs);
377 ctx.strokeStyle = "#A0A0A0";
378 for (let i=1 ; i<8 ; i++) {
380 let x = b.sliderOfs + i/8 * (h - 2 * b.sliderOfs);
381 ctx.moveTo(w / 2 + 5, x);
382 ctx.lineTo(w / 2 + 7, x);
386 ctx.fillStyle = "#E0E0E0";
387 ctx.strokeStyle = "#C0C0C0";
389 ctx.arc(w / 2, knob, 10, 0, 2 * Math.PI);
393 if (b.dataset?.scale === "1") {
394 ctx.font = "14px sans-serif";
395 ctx.strokeStyle = "#808080";
396 ctx.fillStyle = "#808080";
397 ctx.textAlign = "center";
398 ctx.textBaseline = "middle";
399 ctx.fillText(b.minValue, w / 2, h - 10);
400 ctx.fillText(b.maxValue, w / 2, 10);
405Controls.prototype.ctrlVSliderSet = function (value) {
406 if (value < this.minValue)
407 value = this.minValue;
408 if (value > this.maxValue)
409 value = this.maxValue;
411 this.position = (value - this.minValue) / (this.maxValue - this.minValue);
415Controls.prototype.ctrlVSliderHandler = function (e) {
418 let b = e.currentTarget;
420 if (b.canvas === undefined) // we can get events from parent node
423 if ((e.buttons === 1 && e.type === "mousemove") || e.type === "click")
425 if (e.type === "touchmove")
426 y = e.changedTouches[e.changedTouches.length - 1].clientY - b.getBoundingClientRect().top;
428 if (e.type === "contextmenu") {
430 b.value = (b.minValue + b.maxValue) / 2;
431 b.contextMenu = true;
432 this.ctrlVSliderDraw(b);
433 let f = b.dataset.update;
435 f = f.substr(0, f.indexOf("("));
436 window[f](b.value, true);
439 if (y !== undefined) {
440 b.contextMenu = false;
441 b.position = 1 - (y - b.sliderOfs) / (b.clientHeight - 2 * b.sliderOfs);
446 b.value = b.minValue + b.position * (b.maxValue - b.minValue);
447 this.ctrlVSliderDraw(b);
448 let f = b.dataset.update;
450 f = f.substr(0, f.indexOf("("));
451 window[f](b.value, false);
454 if (e.type === "mouseup" && !b.contextMenu) {
455 let f = b.dataset.update;
457 f = f.substr(0, f.indexOf("("));
458 window[f](b.value, true);
462Controls.prototype.ctrlHSliderDraw = function (b) {
465 let w = b.canvas.width;
466 let h = b.canvas.height;
468 let ctx = b.canvas.getContext("2d");
469 ctx.fillStyle = "#E0E0E0";
470 ctx.fillRect(0, 0, b.canvas.width, b.canvas.height);
473 if (b.dataset?.scale === "1") {
474 ctx.font = "14px sans-serif";
475 let o = ctx.measureText(String(b.minValue)).width + 10;
478 o = ctx.measureText(String(b.maxValue)).width + 10;
483 let knob = b.sliderOfs + (b.position) * (w - 2 * b.sliderOfs);
485 ctx.strokeStyle = "#A0A0A0";
488 ctx.moveTo(w - b.sliderOfs, h / 2);
489 ctx.lineTo(knob, h / 2);
492 ctx.strokeStyle = "#00A0FF";
494 ctx.moveTo(knob, h / 2);
495 ctx.lineTo(b.sliderOfs, h / 2);
499 ctx.strokeStyle = "#A0A0A0";
500 for (let i=1 ; i<8 ; i++) {
502 let x = b.sliderOfs + i/8 * (w - 2 * b.sliderOfs);
503 ctx.moveTo(x, h / 2 + 5);
504 ctx.lineTo(x, h / 2 + 7);
508 ctx.fillStyle = "#E0E0E0";
509 ctx.strokeStyle = "#C0C0C0";
511 ctx.arc(knob, h / 2, 10, 0, 2 * Math.PI);
515 if (b.dataset?.scale === "1") {
516 ctx.font = "14px sans-serif";
517 ctx.strokeStyle = "#808080";
518 ctx.fillStyle = "#808080";
519 ctx.textAlign = "center";
520 ctx.textBaseline = "middle";
521 ctx.fillText(b.minValue, b.sliderOfs / 2, h / 2);
522 ctx.fillText(b.maxValue, w - b.sliderOfs / 2, h / 2);
526Controls.prototype.ctrlHSliderSet = function (value) {
527 if (value < this.minValue)
528 value = this.minValue;
529 if (value > this.maxValue)
530 value = this.maxValue;
532 this.position = (value - this.minValue) / (this.maxValue - this.minValue);
536Controls.prototype.ctrlHSliderHandler = function (e) {
539 let b = e.currentTarget;
541 if (b.canvas === undefined) // we can get events from parent node
544 if ((e.buttons === 1 && e.type === "mousemove") || e.type === "click")
546 if (e.type === "touchmove")
547 x = e.changedTouches[e.changedTouches.length - 1].clientX - b.getBoundingClientRect().left;
549 if (e.type === "contextmenu") {
551 b.value = (b.minValue + b.maxValue) / 2;
552 b.contextMenu = true;
553 this.ctrlHSliderDraw(b);
554 let f = b.dataset.update;
556 f = f.substr(0, f.indexOf("("));
557 window[f](b.value, true);
560 if (x !== undefined) {
561 b.contextMenu = false;
562 b.position = (x - b.sliderOfs) / (b.clientWidth - 2 * b.sliderOfs);
567 b.value = b.minValue + b.position * (b.maxValue - b.minValue);
568 this.ctrlHSliderDraw(b);
569 let f = b.dataset.update;
571 f = f.substr(0, f.indexOf("("));
572 window[f](b.value, false);
575 if (e.type === "mouseup" && !b.contextMenu) {
576 let f = b.dataset.update;
578 f = f.substr(0, f.indexOf("("));
579 window[f](Number(b.value), true);
583Controls.prototype.ctrlProgressSet = function (value) {
588 this.firstChild.style.width = Math.round(parseInt(this.style.width) * value) + "px";
591//-------------------------------------------------------------------------------------------------
592var dlgLoadedDialogs = [];
594function dlgLoad(url) {
595 // check if dialog already laoded
596 if (dlgLoadedDialogs.includes(url))
599 dlgLoadedDialogs.push(url);
601 // load dialog via AJAX
602 return new Promise(function (resolve, reject) {
603 let xhr = new XMLHttpRequest();
604 xhr.onreadystatechange = function () {
605 if (xhr.readyState === 4) {
606 if (xhr.status === 200) {
607 let d = document.createElement("div");
608 d.innerHTML = xhr.responseText;
609 document.body.appendChild(d);
610 resolve(xhr.responseText);
612 dlgAlert("network error: see javascript console: dlgLoad() cannot load " + url + ", HTTP status: " + xhr.status);
613 reject(xhr.responseURL);
618 xhr.open("GET", url, true);
619 xhr.setRequestHeader('Content-type', 'text/html');
624function drawCloseButton(c, mark) {
627 let ctx = c.getContext("2d");
628 ctx.clearRect(0, 0, c.width, c.height);
631 ctx.arc(c.width/2, c.height/2, c.width/2-1, 0, 2*Math.PI);
632 ctx.fillStyle = '#FD5E59';
634 ctx.strokeStyle = '#DF2020';
637 ctx.strokeStyle = '#000000';
640 ctx.moveTo(c.width/2-3, c.height/2-3);
641 ctx.lineTo(c.width/2+3, c.height/2+3);
642 ctx.moveTo(c.width/2+3, c.height/2-3);
643 ctx.lineTo(c.width/2-3, c.height/2+3);
648function dlgCenter(dlg) {
650 if (typeof dlg === "string")
651 d = document.getElementById(dlg);
656 dlgAlert("Dialog '" + dlg + "' does not exist");
660 let doc = document.documentElement;
661 d.style.left = Math.round(doc.clientWidth / 2 + doc.scrollLeft - d.offsetWidth / 2) + "px";
662 if (doc.clientHeight / 2 - d.offsetHeight / 2 < 0)
665 d.style.top = Math.round(doc.clientHeight / 2 + doc.scrollTop - d.offsetHeight / 2) + "px";
667 // allow for scrolling of very high dialog boxes
668 if (d.offsetHeight > document.documentElement.clientHeight)
669 d.style.position = "absolute";
672function checkResize(dlg, e) {
673 const rect = dlg.getBoundingClientRect();
677 dlg.resizeLeft = false;
678 dlg.resizeRight = false;
679 dlg.resizeTop = false;
680 dlg.resizeBottom = false;
685 if (mx >= rect.left && mx <= rect.right && my >= rect.top && my <= rect.bottom) {
686 if (mx < rect.left + margin) { // left side
687 dlg.resizeLeft = true;
688 if (my < rect.top + margin) {
689 dlg.resizeTop = true;
691 } else if (my > rect.bottom - margin) {
692 dlg.resizeBottom = true;
696 } else if (mx > rect.right - margin) { // right side
697 dlg.resizeRight = true;
698 if (my < rect.top + margin) {
699 dlg.resizeTop = true;
701 } else if (my > rect.bottom - margin) {
702 dlg.resizeBottom = true;
706 } else if (my > rect.bottom - margin) { // bottom side
707 dlg.resizeBottom = true;
712 // safari has limited set of cursors
713 if (navigator.vendor === 'Apple Computer, Inc.' && c !== '')
716 if (e.target.className === 'dlgTitlebar') {
719 e.target.style.cursor = 'move';
721 e.target.style.cursor = 'pointer';
723 e.target.style.cursor = c;
725 dlg.style.cursor = c;
727 if (c !== '' && c !== 'pointer') {
732function dlgShow(dlg, modal, param) {
734 if (typeof dlg === "string")
735 d = document.getElementById(dlg);
740 dlgAlert("Dialog '" + dlg + "' does not exist");
744 if (d.childNodes === undefined) {
745 dlgAlert("Dialog '" + dlg + "' has no title bar");
749 // add optional parameter to dialog
752 // put "close" icon into title bar
754 if (d.childNodes[0].className === "dlgTitlebar")
756 if (d.childNodes[1] && d.childNodes[1].className === "dlgTitlebar")
758 if (t !== undefined) {
759 let ttext = t.innerHTML;
760 if (ttext.search('dlgHide') === -1) {
761 t.innerHTML = "<div style=\"position: absolute;left: 6px;top: 3px;\" " +
762 "onclick=\"dlgClose(this);\">" +
763 "<canvas id=\"cvsClose\" width=\"14px\" height=\"14px\"></canvas>" +
766 d.canvas = t.childNodes[0].childNodes[0];
767 drawCloseButton(d.canvas, false);
770 d.dragActive = false;
771 d.resizeActive = false;
772 d.dragMouseX = undefined;
773 d.dragMouseY = undefined;
774 d.dragStartLeft = undefined;
775 d.dragStartTop = undefined;
776 d.dragStartRight = undefined;
777 d.dragStartBottom = undefined;
778 d.modal = (modal === true);
780 d.style.display = "flex";
783 // put dialog on top of all other dialogs
784 let dlgs = document.getElementsByClassName("dlgFrame");
785 for (let i = 0; i < dlgs.length; i++)
786 dlgs[i].style.zIndex = "30"; // on top of blackout (20)
787 d.style.zIndex = "31";
789 // move dialog right-down if on top of previous one
790 if (dlgs.length > 1) {
791 d.style.left = (parseInt(dlgs[dlgs.length - 2].style.left) + 30).toString() + "px";
792 d.style.top = (parseInt(dlgs[dlgs.length - 2].style.top) + 30).toString() + "px";
795 // enable scrolling if dialog box goes beyond screen
796 d.oldScroll = window.getComputedStyle(document.body).overflow;
797 document.body.style.overflow = "scroll";
800 let b = document.getElementById("dlgBlackout");
801 if (b === undefined || b === null) {
802 b = document.createElement("div");
803 b.id = "dlgBlackout";
804 b.className = "dlgBlackout";
805 document.body.appendChild(b);
808 b.style.display = "flex";
812 d.dlgMouseDown = function (e) {
813 if (!d.modal && !d.contains(e.target) && !this.dragActive && !this.resizeActive)
815 if (d.style.display === "none")
817 if (e.target.tagName === "INPUT" || e.target.tagName === "SELECT")
820 // ignore right mouse clicks
828 if (d.resizeLeft || d.resizeRight ||
829 d.resizeTop || d.resizeBottom)
830 d.resizeActive = true;
831 else if (e.target.className === "dlgTitlebar")
832 this.dragActive = true;
834 const rect = d.getBoundingClientRect();
836 this.dragMouseX = e.clientX;
837 this.dragMouseY = e.clientY;
838 this.dragStartLeft = rect.left;
839 this.dragStartTop = rect.top;
840 this.dragStartRight = rect.right;
841 this.dragStartBottom = rect.bottom;
843 if (d.modal && e.target !== d && !d.contains(e.target)) {
844 // catch all mouse events outside the dialog
847 if (e.target === this || d.contains(e.target)) {
848 let dlgs = document.getElementsByClassName("dlgFrame");
849 for (let i = 0; i < dlgs.length; i++)
850 dlgs[i].style.zIndex = "30";
851 d.style.zIndex = "31";
856 d.dlgMouseMove = function (e) {
857 if (!d.contains(e.target) && !this.dragActive && !this.resizeActive)
859 if (d.style.display === "none")
862 // draw close button with "x" if mouse cursor is inside
863 drawCloseButton(d.canvas, e.target === d.canvas);
865 // show resize cursors
866 if (!this.resizeActive)
870 if (this.dragActive) {
875 // stop dragging if leaving window
876 if (x < 0 || y < 0 ||
877 x > document.documentElement.clientWidth ||
878 y > document.documentElement.clientHeight ||
879 (this.dragStartTop + (y - this.dragMouseY)) < 0) {
880 this.dragActive = false;
882 this.style.left = (this.dragStartLeft + (x - this.dragMouseX)) + "px";
883 this.style.top = (this.dragStartTop + (y - this.dragMouseY)) + "px";
888 if (this.resizeActive) {
893 if (x < 0 || y < 0 ||
894 x > document.documentElement.clientWidth ||
895 y > document.documentElement.clientHeight) {
896 this.resizeActive = false;
898 if (this.resizeLeft) {
899 this.style.left = (this.dragStartLeft + (x - this.dragMouseX)) + "px";
900 this.style.width = (this.dragStartRight - this.dragStartLeft - (x - this.dragMouseX)) + "px";
902 if (this.resizeTop) {
903 this.style.top = (this.dragStartTop + (y - this.dragMouseY)) + "px";
904 this.style.height = (this.dragStartBottom - this.dragStartTop - (y - this.dragMouseY)) + "px";
906 if (this.resizeRight) {
907 this.style.width = (this.dragStartRight - this.dragStartLeft + (x - this.dragMouseX)) + "px";
909 if (this.resizeBottom) {
910 this.style.height = (this.dragStartBottom - this.dragStartTop + (y - this.dragMouseY)) + "px";
916 d.dlgMouseUp = function (e) {
917 if (!d.contains(e.target) && !this.dragActive && !this.resizeActive)
919 this.dragActive = false;
920 this.resizeActive = false;
923 d.dlgTouchStart = function (e) {
924 if (d.style.display === "none")
927 if ((e.target === this || e.target.parentNode === this) &&
928 e.target.className === "dlgTitlebar") {
930 this.dragMouseX = e.targetTouches[0].clientX;
931 this.dragMouseY = e.targetTouches[0].clientY;
932 this.dragStartLeft = parseInt(this.style.left);
933 this.dragStartTop = parseInt(this.style.top);
936 if (d.modal && e.target !== d && !d.contains(e.target)) {
937 // catch all mouse events
940 if (e.target === this || d.contains(e.target)) {
941 let dlgs = document.getElementsByClassName("dlgFrame");
942 for (let i = 0; i < dlgs.length; i++)
943 dlgs[i].style.zIndex = "30";
944 d.style.zIndex = "31";
949 d.dlgTouchMove = function (e) {
950 if (!d.contains(e.target) && !this.dragActive && !this.resizeActive)
952 if (d.style.display === "none")
955 if (this.dragMouseX > 0 && this.dragMouseY > 0) {
957 let x = e.changedTouches[e.changedTouches.length - 1].clientX;
958 let y = e.changedTouches[e.changedTouches.length - 1].clientY;
959 this.style.left = (this.dragStartLeft + (x - this.dragMouseX)) + "px";
960 this.style.top = (this.dragStartTop + (y - this.dragMouseY)) + "px";
964 d.dlgTouchEnd = function (e) {
965 if (!d.contains(e.target) && !this.dragActive && !this.resizeActive)
967 if (d.style.display === "none")
970 if (this.dragMouseX > 0 && this.dragMouseY > 0) {
977 d.dlgTouchCancel = function (e) {
978 if (d.style.display === "none")
981 if (this.dragMouseX > 0 && this.dragMouseY > 0) {
988 d.dlgKeyDown = function (e) {
989 if (d.style.display === "none")
992 if (e.key === 'Escape') {
994 dlgClose(d.childNodes[1].childNodes[0]);
997 window.addEventListener("keydown", d.dlgKeyDown.bind(d), true);
999 window.addEventListener("mousedown", d.dlgMouseDown.bind(d), true);
1000 window.addEventListener("mousemove", d.dlgMouseMove.bind(d), true);
1001 window.addEventListener("mouseup", d.dlgMouseUp.bind(d), true);
1002 window.addEventListener("touchstart", d.dlgTouchStart.bind(d), true);
1003 window.addEventListener("touchmove", d.dlgTouchMove.bind(d), true);
1004 window.addEventListener("touchend", d.dlgTouchEnd.bind(d), true);
1005 window.addEventListener("touchcancel", d.dlgTouchCancel.bind(d), true);
1008function dlgMove(d, x, y) {
1009 d.style.left = x + "px";
1010 d.style.top = y + "px";
1013function dlgClose(div) {
1015 while (!dlg.className.includes("dlgFrame"))
1016 dlg = dlg.parentElement;
1017 if (dlg.shouldDestroy)
1018 dlgMessageDestroy(dlg);
1019 else if (dlg.id !== undefined)
1023function dlgHide(dlg) {
1024 if (typeof dlg === "string")
1025 dlg = document.getElementById(dlg);
1026 else if (dlg.type === "button") {
1028 dlg = dlg.parentElement;
1029 } while (dlg.className !== 'dlgFrame');
1033 // only remove blackout if we are the only modal dialog left
1034 let dlgs = document.getElementsByClassName("dlgFrame");
1036 for (let i = 0; i < dlgs.length; i++)
1037 if (dlgs[i].style.display !== "none" && dlgs[i].modal)
1040 let d = document.getElementById("dlgBlackout");
1041 if (d !== undefined && d !== null)
1042 d.style.display = "none";
1045 dlg.style.display = "none";
1046 if (dlg.oldScroll !== "")
1047 document.body.style.overflow = dlg.oldScroll;
1050function dlgMessageDestroy(b) {
1052 while (!dlg.className.includes("dlgFrame"))
1053 dlg = dlg.parentElement;
1055 // only remove blackout if we are the only modal dialog left
1056 let dlgs = document.getElementsByClassName("dlgFrame");
1058 for (let i = 0; i < dlgs.length; i++)
1059 if (dlgs[i].style.display !== "none" && dlgs[i].modal)
1062 let d = document.getElementById("dlgBlackout");
1063 if (d !== undefined && d !== null)
1064 d.style.display = "none";
1067 // dialog is not really removed from memory, event listerner is still active and
1068 // grabs mousdown events, so mark its display "none" to prevent eating mouse events
1069 // above in dlgMouseDown routine
1070 dlg.style.display = "none";
1071 document.body.removeChild(dlg);
1074function dlgMessage(title, string, modal, error, callback, param) {
1075 let d = document.createElement("div");
1076 d.className = "dlgFrame";
1077 d.style.zIndex = modal ? "31" : "30";
1078 d.callback = callback;
1079 d.callbackParam = param;
1080 d.shouldDestroy = true;
1082 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">" + title + "</div>" +
1083 "<div class=\"dlgPanel\" style=\"padding: 30px;\">" +
1084 "<div id=\"dlgMessageString\">" + string + "</div>" +
1085 "<br /><br /><button class=\"dlgButton\" id=\"dlgMessageButton\" type=\"button\" " +
1086 " onClick=\"let d=this.parentElement.parentElement;if(d.callback!==undefined)d.callback(d.callbackParam);dlgMessageDestroy(this)\">Close</button>" +
1088 document.body.appendChild(d);
1090 if (error === true) {
1091 let t = document.getElementById("dlgMessageTitle");
1092 t.style.backgroundColor = "#9E2A2A";
1093 t.style.color = "white";
1100function dlgAlert(s, callback) {
1101 dlgMessage('Message', s, true, false, callback);
1104function dlgConfirm(string, confirmCallback, param) {
1105 let d = document.createElement("div");
1106 d.className = "dlgFrame";
1107 d.style.zIndex = "31";
1108 d.callback = confirmCallback;
1109 d.callbackParam = param;
1110 d.shouldDestroy = true;
1112 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">Please confirm</div>" +
1113 "<div class=\"dlgPanel\" style=\"padding: 30px;\">" +
1114 "<div id=\"dlgMessageString\">" + string + "</div>" +
1116 "<button class=\"dlgButtonDefault\" id=\"dlgMessageButtonOk\" type=\"button\" " +
1117 " onClick=\"let d=this.parentElement.parentElement;d.callback(true,d.callbackParam);dlgMessageDestroy(this);\">OK</button>" +
1118 "<button class=\"dlgButton\" id=\"dlgMessageButtonCancel\" type=\"button\" " +
1119 " onClick=\"let d=this.parentElement.parentElement;d.callback(false,d.callbackParam);dlgMessageDestroy(this);\">Cancel</button>" +
1122 document.body.appendChild(d);
1125 document.getElementById('dlgMessageButtonOk').focus();
1129// Populate a modal with a general html
1130function dlgGeneral(p) {
1131 /* general dialog containing p.html code and other optional parameters
1132 p.iddiv - the name of the dialog div (optional)
1133 p.width/p.height - initial width/height of dialog (optional)
1134 p.minWidth/p.minHeight - minimal width/height of dialog (optional)
1135 p.x/p.y - initial position of dialog (optional)
1136 p.title - title of the dialog (optional)
1139 // First make sure you removed existing iddiv
1140 if (document.getElementById(p.iddiv)) document.getElementById(p.iddiv).remove();
1141 let d = document.createElement("div");
1142 d.className = "dlgFrame";
1143 if (p.iddiv) d.id = p.iddiv;
1144 d.style.zIndex = "30";
1145 d.style.overflow = "hidden";
1146 d.style.width = p.width ? p.width + "px" : "400px";
1147 d.style.height = p.height ? p.height + "px" : "200px";
1148 d.style.minWidth = p.minWidth ? p.minWidth + "px" : d.style.width;
1149 d.style.minHeight = p.minHeight ? p.minHeight + "px" : d.style.height;
1150 d.style.maxHeight = "90vh";
1151 d.style.maxWidth = "60vw";
1152 d.shouldDestroy = true;
1154 const dlgTitle = document.createElement("div");
1155 dlgTitle.className = "dlgTitlebar";
1156 //dlgTitle.id = "dlgMessageTitle";
1157 dlgTitle.innerText = p.title || (p.iddiv ? p.iddiv + " dialog" : "General dialog");
1158 d.appendChild(dlgTitle);
1160 const dlgPanel = document.createElement("div");
1161 dlgPanel.className = "dlgPanel";
1162 //dlgPanel.id = "dlgPanel";
1163 d.appendChild(dlgPanel);
1165 const content = document.createElement("div");
1166 //content.id = "dlgHTML";
1167 content.style.overflow = "auto";
1168 content.innerHTML = p.html;
1169 dlgPanel.appendChild(content);
1171 document.body.appendChild(d);
1174 if (p.x !== undefined && p.y !== undefined)
1175 dlgMove(d, p.x, p.y);
1177 // Initial size based on content
1178 d.style.height = (p.height !== undefined) ? p.height : (content.scrollHeight + dlgTitle.offsetHeight + 5 ) + "px";
1179 d.style.width = (p.width !== undefined) ? p.width : (content.scrollWidth + 5 ) + "px";
1181 // Function to handle resize events
1182 function handleResize() {
1183 content.style.height = (d.offsetHeight - dlgTitle.offsetHeight - 5 ) + "px";
1186 // Resize observer to watch for dialog resizing
1187 const resizeObs = new ResizeObserver(handleResize);
1188 resizeObs.observe(d);
1193function dlgQueryKeyDown(event, inp) {
1194 let keyCode = ('which' in event) ? event.which : event.keyCode;
1196 if (keyCode === 27) {
1198 let d = inp.parentElement.parentElement.parentElement;
1199 d.callback(false, d.callbackParam);
1200 dlgMessageDestroy(inp.parentElement);
1204 if (keyCode === 13) {
1206 let d = inp.parentElement.parentElement.parentElement;
1207 d.callback(inp.value, d.callbackParam);
1208 dlgMessageDestroy(inp.parentElement);
1215function dlgQuery(string, value, queryCallback, param, size) {
1216 let d = document.createElement("div");
1217 d.className = "dlgFrame";
1218 d.style.zIndex = "31";
1219 d.callback = queryCallback;
1220 d.callbackParam = param;
1221 d.shouldDestroy = true;
1223 if (size === undefined)
1226 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\"> </div>" +
1227 "<div class=\"dlgPanel\" style=\"padding: 20px;\">" +
1228 "<div id=\"dlgMessageString\">" + string + " <input type='text' size='"+size+"' id='dlgQueryInput' onkeydown='return dlgQueryKeyDown(event, this);' value='" + value + "'></input></div>" +
1230 "<button class=\"dlgButtonDefault\" id=\"dlgMessageButton\" type=\"button\" " +
1231 " onClick=\"let d=this.parentElement.parentElement;d.callback(document.getElementById('dlgQueryInput').value,d.callbackParam);dlgMessageDestroy(this);\">OK</button>" +
1232 "<button class=\"dlgButton\" id=\"dlgMessageButton\" type=\"button\" " +
1233 " onClick=\"let d=this.parentElement.parentElement;d.callback(false,d.callbackParam);dlgMessageDestroy(this);\">Cancel</button>" +
1236 document.body.appendChild(d);
1239 document.getElementById('dlgQueryInput').focus();
1240 document.getElementById('dlgQueryInput').select();
1249let dlgWaitFuncParam;
1251function dlgWait(time, string, func, param) {
1253 let d = document.createElement("div");
1254 d.className = "dlgFrame";
1255 d.style.zIndex = "31";
1256 d.shouldDestroy = true;
1258 <!-- wait dialog -->
1259 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">Please wait...</div>" +
1260 "<div class=\"dlgPanel\" style=\"padding: 20px;\">" +
1261 "<div id=\"dlgMessageString\">" + string + "</div>" +
1263 "<div name=\"ctrlProgress\" style=\"margin:auto;width:250px;\" id=\"dlgWaitProgress\"></div>" +
1266 document.body.appendChild(d);
1268 // init progress bar
1269 let p = document.getElementById('dlgWaitProgress');
1270 p.className = "ctrlProgress";
1271 let ind = document.createElement("div");
1272 ind.className = "ctrlProgressInd";
1273 ind.style.height = p.style.height;
1274 ind.style.backgroundColor = p.style.color;
1276 p.set = function (value) {
1277 this.firstChild.style.width = Math.round(parseInt(this.style.width) * value) + "px";
1283 dlgWaitProgress = 0;
1286 dlgWaitFuncParam = param;
1287 window.setTimeout(updateDlgWaitProgress, 100);
1291function updateDlgWaitProgress() {
1292 dlgWaitProgress += 0.1;
1293 let d = document.getElementById("dlgWaitProgress");
1294 d.set(dlgWaitProgress / dlgWaitTime);
1295 if (dlgWaitProgress >= dlgWaitTime) {
1296 dlgWaitDialog.style.display = "none";
1297 document.body.removeChild(dlgWaitDialog);
1298 let d = document.getElementById("dlgBlackout");
1299 if (d !== undefined && d !== null)
1300 d.style.display = "none";
1301 if (dlgWaitFunc !== undefined)
1302 dlgWaitFunc(dlgWaitFuncParam);
1304 window.setTimeout(updateDlgWaitProgress, 100);