Fixes for restoreSession logic. (#4378)
[openemr.git] / library / dialog.js
blobb375a9499562cac8bea8fb93bc77545006925216
1 // Copyright (C) 2005 Rod Roark <rod@sunsetsystems.com>
2 // Copyright (C) 2018-2020 Jerry Padgett <sjpadgett@gmail.com>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
9 (function (define) {
10     define(['jquery'], function ($, root) {
11         let opts_default = {};
12         root = root || {};
13         root.alert = alert;
14         root.ajax = ajax;
15         root.confirm = confirm;
16         root.closeAjax = closeAjax;
17         root.close = close;
19         return root;
21         function ajax(data) {
22             let opts = {
23                 buttons: data.buttons,
24                 allowDrag: data.allowDrag,
25                 allowResize: data.allowResize,
26                 sizeHeight: data.sizeHeight,
27                 type: data.type,
28                 resolvePromiseOn: data.resolvePromiseOn,
29                 data: data.data,
30                 url: data.url,
31                 dataType: data.dataType // xml/json/text etc.
32             };
34             let title = data.title;
36             return dlgopen('', '', data.size, 0, '', title, opts);
37         }
39         function alert(data, title) {
40             title = title ? title : 'Alert';
41             let alertTitle = '<span class="text-danger bg-light"><i class="fa fa-exclamation-triangle"></i>&nbsp;' + title + '</span>';
42             return dlgopen('', '', 675, 0, '', alertTitle, {
43                 buttons: [
44                     {text: '<i class="fa fa-thumbs-up mr-1"></i>OK', close: true, style: 'primary'}
45                 ],
46                 type: 'Alert',
47                 sizeHeight: 'auto',
48                 html: '<p>' + data + '</p>'
49             });
50         }
52         function confirm(data, title) {
53             title = title ? title : 'Confirm';
54             let alertTitle = '<span class="text-info bg-light"><i class="fa fa-exclamation-triangle"></i>&nbsp;' + title + '</span>';
55             return dlgopen('', '', "modal-md", 0, '', alertTitle, {
56                 buttons: [
57                     {text: '<i class="fa fa-thumbs-up mr-1"></i>Yes', close: true, id: 'confirmYes', style: 'primary'},
58                     {text: '<i class="fa fa-thumbs-down mr-1"></i>No', close: true, id: 'confirmNo', style: 'primary'},
59                     {text: 'Nevermind', close: true, style: 'secondary'}
60                 ],
61                 type: 'Confirm',
62                 resolvePromiseOn: 'confirm',
63                 sizeHeight: 'auto',
64                 html: '<p>' + data + '</p>'
65             });
66         }
68         function closeAjax() {
69             dlgCloseAjax();
70         }
72         function close() {
73             dlgclose();
74         }
75     });
77     if (typeof window.xl !== 'function') {
78         (async (utilfn) => {
79             await includeScript(utilfn, 'script');
80         })(top.webroot_url + '/library/js/utility.js').then(() => {
81             console.log('Utilities Unavailable! loading:[ ' + utilfn + ' ] For: [ ' + location + ' ]');
82         });
83     }
84 }(typeof define == 'function' && define.amd ?
85     define :
86     function (args, mName) {
87         this.dialog = typeof module != 'undefined' && module.exports ?
88             mName(require(args[0], {}), module.exports) :
89             mName(window.$);
90     }));
93 // open a new cascaded window
94 function cascwin(url, winname, width, height, options) {
95     var mywin = window.parent ? window.parent : window;
96     var newx = 25, newy = 25;
97     if (!isNaN(mywin.screenX)) {
98         newx += mywin.screenX;
99         newy += mywin.screenY;
100     } else if (!isNaN(mywin.screenLeft)) {
101         newx += mywin.screenLeft;
102         newy += mywin.screenTop;
103     }
104     if ((newx + width) > screen.width || (newy + height) > screen.height) {
105         newx = 0;
106         newy = 0;
107     }
108     if (typeof top.restoreSession === 'function') {
109         top.restoreSession();
110     }
112     // MS IE version detection taken from
113     // http://msdn2.microsoft.com/en-us/library/ms537509.aspx
114     // to adjust the height of this box for IE only -- JRM
115     if (navigator.appName == 'Microsoft Internet Explorer') {
116         var ua = navigator.userAgent;
117         var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
118         if (re.exec(ua) != null)
119             rv = parseFloat(RegExp.$1); // this holds the version number
120         height = height + 28;
121     }
123     retval = window.open(url, winname, options +
124         ",width=" + width + ",height=" + height +
125         ",left=" + newx + ",top=" + newy +
126         ",screenX=" + newx + ",screenY=" + newy);
128     return retval;
131 // recursive window focus-event grabber
132 function grabfocus(w) {
133     for (var i = 0; i < w.frames.length; ++i) grabfocus(w.frames[i]);
134     w.onfocus = top.imfocused;
137 // Call this when a "modal" windowed dialog is desired.
138 // Note that the below function is free standing for either
139 // ui's.Use dlgopen() for responsive b.s modal dialogs.
140 // Can now use anywhere to cascade natives...12/1/17 sjp
142 function dlgOpenWindow(url, winname, width, height) {
143     if (top.modaldialog && !top.modaldialog.closed) {
144         if (window.focus) top.modaldialog.focus();
145         if (top.modaldialog.confirm(top.oemr_dialog_close_msg)) {
146             top.modaldialog.close();
147             top.modaldialog = null;
148         } else {
149             return false;
150         }
151     }
152     top.modaldialog = cascwin(url, winname, width, height,
153         "resizable=1,scrollbars=1,location=0,toolbar=0");
155     return false;
158 // This is called from del_related() which in turn is invoked by find_code_dynamic.php.
159 // Deletes the specified codetype:code from the indicated input text element.
160 function my_del_related(s, elem, usetitle) {
161     if (!s) {
162         // Deleting everything.
163         elem.value = '';
164         if (usetitle) {
165             elem.title = '';
166         }
167         return;
168     }
169     // Convert the codes and their descriptions to arrays for easy manipulation.
170     var acodes = elem.value.split(';');
171     var i = acodes.indexOf(s);
172     if (i < 0) {
173         return; // not found, should not happen
174     }
175     // Delete the indicated code and description and convert back to strings.
176     acodes.splice(i, 1);
177     elem.value = acodes.join(';');
178     if (usetitle) {
179         var atitles = elem.title.split(';');
180         atitles.splice(i, 1);
181         elem.title = atitles.join(';');
182     }
185 function dialogID() {
186     function s4() {
187         return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
188     }
190     return s4() + s4() + s4() + s4() + s4() + +s4() + s4() + s4();
193 // test for and/or remove dependency.
194 function inDom(dependency, type, remove) {
195     let el = type;
196     let attr = type === 'script' ? 'src' : type === 'link' ? 'href' : 'none';
197     let all = document.getElementsByTagName(el);
198     for (let i = all.length; i > -1; i--) {
199         if (all[i] && all[i].getAttribute(attr) !== null && all[i].getAttribute(attr).indexOf(dependency) !== -1) {
200             if (remove) {
201                 all[i].parentNode.removeChild(all[i]);
202                 console.log("Removed from DOM: " + dependency);
203                 return true;
204             } else {
205                 return true;
206             }
207         }
208     }
209     return false;
212 // test to see if bootstrap theming is loaded (via standard or custom bootstrap library)
213 //  Will check for the badge-secondary class
214 //   - if exist, then assume bootstrap loaded
215 //   - if not exist, then assume bootstrap not loaded
216 function isBootstrapCss() {
217     for (let i = 0; i < document.styleSheets.length; i++) {
218         let rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules;
219         for (let x in rules) {
220             if (rules[x].selectorText == '.badge-secondary') {
221                 return true;
222             }
223         }
224     }
225     return false;
228 // These functions may be called from scripts that may be out of scope with top so...
229 // if opener is tab then we need to be in tabs UI scope and while we're at it, let's bring webroot along...
231 if (typeof top.webroot_url === "undefined" && opener) {
232     if (typeof opener.top.webroot_url !== "undefined") {
233         top.webroot_url = opener.top.webroot_url;
234     }
236 // We'll need these if out of scope
238 if (typeof top.set_opener !== "function") {
239     var opener_list = [];
241     function set_opener(window, opener) {
242         top.opener_list[window] = opener;
243     }
245     function get_opener(window) {
246         return top.opener_list[window];
247     }
250 // universal alert popup message
251 if (typeof alertMsg !== "function") {
252     function alertMsg(message, timer = 5000, type = 'danger', size = '', persist = '') {
253         // this xl() is just so cool.
254         let gotIt = xl("Got It");
255         let title = xl("Alert");
256         let dismiss = xl("Dismiss");
257         $('#alert_box').remove();
258         let oHidden = '';
259         oHidden = !persist ? "hidden" : '';
260         let oSize = (size == 'lg') ? 'left:10%;width:80%;' : 'left:25%;width:50%;';
261         let style = "position:fixed;top:25%;" + oSize + " bottom:0;z-index:9999;";
262         $("body").prepend("<div class='container text-center' id='alert_box' style='" + style + "'></div>");
263         let mHtml = '<div id="alertmsg" class="alert alert-' + type + ' alert-dismissable">' +
264             '<button type="button" class="btn btn-link ' + oHidden + '" id="dontShowAgain" data-dismiss="alert">' +
265             gotIt + '&nbsp;<i class="fa fa-thumbs-up"></i></button>' +
266             '<h4 class="alert-heading text-center">' + title + '!</h4><hr>' + '<p style="color:#000;">' + message + '</p>' +
267             '<button type="button" class="pull-right btn btn-link" data-dismiss="alert">' + dismiss + '</button><br /></div>';
268         $('#alert_box').append(mHtml);
269         $('#alertmsg').on('closed.bs.alert', function () {
270             clearTimeout(AlertMsg);
271             $('#alert_box').remove();
272             return false;
273         });
274         $('#dontShowAgain').on('click', function (e) {
275             persistUserOption(persist, 1);
276         });
277         let AlertMsg = setTimeout(function () {
278             $('#alertmsg').fadeOut(800, function () {
279                 $('#alert_box').remove();
280             });
281         }, timer);
282     }
284     const persistUserOption = function (option, value) {
285         return $.ajax({
286             url: top.webroot_url + "/library/ajax/user_settings.php",
287             type: 'post',
288             contentType: 'application/x-www-form-urlencoded',
289             data: {
290                 csrf_token_form: top.csrf_token_js,
291                 target: option,
292                 setting: value
293             },
294             beforeSend: function () {
295                 if (typeof top.restoreSession === 'function') {
296                     top.restoreSession();
297                 }
298             },
299             error: function (jqxhr, status, errorThrown) {
300                 console.log(errorThrown);
301             }
302         });
303     };
307 // Test if supporting dialog callbacks and close dependencies are in scope.
308 // This is useful when opening and closing the dialog is in the same scope. Still use include_opener.js
309 // in script that will close a dialog that is not in the same scope dlgopen was used
310 // or use parent.dlgclose() if known decedent.
311 // dlgopen() will always have a name whether assigned by dev or created by function.
312 // Callback, onClosed and button clicks are still available either way.
313 // For a callback on close use: dlgclose(functionName, farg1, farg2 ...) which becomes: functionName(farg1,farg2, etc)
315 if (typeof dlgclose !== "function") {
316     if (!opener) {
317         opener = window;
318     }
320     function dlgclose(call, args) {
321         var frameName = window.name;
322         var wframe = top;
323         if (frameName === '') {
324             // try to find dialog. dialogModal is embedded dialog class
325             // It has to be here somewhere.
326             frameName = $(".dialogModal").attr('id');
327             if (!frameName) {
328                 frameName = parent.$(".dialogModal").attr('id');
329                 if (!frameName) {
330                     console.log("Unable to find dialog.");
331                     return false;
332                 }
333             }
334         }
335         var dialogModal = top.$('div#' + frameName);
337         var removeFrame = dialogModal.find("iframe[name='" + frameName + "']");
338         if (removeFrame.length > 0) {
339             removeFrame.remove();
340         }
342         if (dialogModal.length > 0) {
343             if (call) {
344                 wframe.setCallBack(call, args); // sets/creates callback function in dialogs scope.
345             }
346             dialogModal.modal('hide');
347         } else {
348             // no opener not iframe must be in here
349             $(this.document).find(".dialogModal").modal('hide');
350         }
351     }
355 * function dlgopen(url, winname, width, height, forceNewWindow, title, opts)
357 * @summary Stackable, resizable and draggable responsive ajax/iframe dialog modal.
359 * @param {url} string Content location.
360 * @param {String} winname If set becomes modal id and/or iframes name. Or, one is created/assigned(iframes).
361 * @param {Number| String} width|modalSize(modal-xl) For sizing: an number will be converted to a percentage of view port width.
362 * @param {Number} height Initial minimum height. For iframe auto resize starts at this height.
363 * @param {boolean} forceNewWindow Force using a native window.
364 * @param {String} title If exist then header with title is created otherwise no header and content only.
365 * @param {Object} opts Dialogs options.
366 * @returns {Object} dialog object reference.
367 * */
368 function dlgopen(url, winname, width, height, forceNewWindow, title, opts) {
369     // First things first...
370     if (typeof top.restoreSession === 'function') {
371         top.restoreSession();
372     }
374     // A matter of Legacy
375     if (forceNewWindow) {
376         return dlgOpenWindow(url, winname, width, height);
377     }
379     // wait for DOM then check dependencies needed to run this feature.
380     // dependency duration is while 'this' is in scope, temporary...
381     // seldom will this get used as more of U.I is moved to Bootstrap
382     // but better to continue than stop because of a dependency...
383     //
384     let jqurl = top.webroot_url + '/public/assets/jquery/dist/jquery.min.js';
385     if (typeof jQuery === 'undefined') {
386         (async (utilfn) => {
387             await includeScript(utilfn, 'script');
388         })(jqurl);
389     }
390     jQuery(function () {
391         // Check for dependencies we will need.
392         // webroot_url is a global defined in main_screen.php or main.php.
393         let bscss = top.webroot_url + '/public/assets/bootstrap/dist/css/bootstrap.min.css';
394         let bscssRtl = top.webroot_url + '/public/assets/bootstrap-v4-rtl/dist/css/bootstrap-rtl.min.css';
395         let bsurl = top.webroot_url + '/public/assets/bootstrap/dist/js/bootstrap.bundle.min.js';
397         let version = jQuery.fn.jquery.split(' ')[0].split('.');
398         if ((version[0] < 2 && version[1] < 9) || (version[0] === 1 && version[1] === 9 && version[2] < 1)) {
399             inDom('jquery-min', 'script', true);
400             (async (utilfn) => {
401                 await includeScript(utilfn, 'script');
402             })(jqurl).then(() => {
403                 console.log('Replacing jQuery version:[ ' + version + ' ]');
404             });
405         }
406         if (!isBootstrapCss()) {
407             (async (utilfn) => {
408                 await includeScript(utilfn, 'link');
409             })(bscss);
410             if (top.jsLanguageDirection == 'rtl') {
411                 (async (utilfn) => {
412                     await includeScript(utilfn, 'link');
413                 })(bscssRtl);
414             }
415         }
416         if (typeof jQuery.fn.modal === 'undefined') {
417             if (!inDom('bootstrap.bundle.min.js', 'script', false)) {
418                 (async (utilfn) => {
419                     await includeScript(utilfn, 'script');
420                 })(bsurl);
421             }
422         }
423     });
425     // onward
426     var opts_defaults = {
427         type: 'iframe', // POST, GET (ajax) or iframe
428         async: true,
429         frameContent: "", // for iframe embedded content
430         html: "", // content for alerts, comfirm etc ajax
431         allowDrag: true,
432         allowResize: true,
433         sizeHeight: 'auto', // 'full' will use as much height as allowed
434         // use is onClosed: fnName ... args not supported however, onClosed: 'reload' is auto defined and requires no function to be created.
435         onClosed: false,
436         allowExternal: false, // allow a dialog window to a URL that is external to the current url
437         callBack: false, // use {call: 'functionName, args: args, args} if known or use dlgclose.
438         resolvePromiseOn: '' // this may be useful values are init, shown, show, confirm, alert and closed which coincide with dialog events.
439     };
441     if (!opts) {
442         opts = {};
443     }
444     opts = jQuery.extend({}, opts_defaults, opts);
445     opts.type = opts.type ? opts.type.toLowerCase() : '';
446     opts.resolvePromiseOn = opts.resolvePromiseOn ?? 'init';
447     var mHeight, mWidth, mSize, msSize, dlgContainer, fullURL, where; // a growing list...
449     where = (opts.type === 'iframe') ? top : window;
451     // get url straight...
452     fullURL = "";
453     if (opts.url) {
454         url = opts.url;
455     }
456     if (url) {
457         if (url[0] === "/") {
458             fullURL = url
459         } else if (opts.allowExternal === true) {
460             var checkUrl = new URL(url);
461             // we only allow http & https protocols to be launched
462             if (checkUrl.protocol === "http:" || checkUrl.protocol == "https:") {
463                 fullURL = url;
464             }
465         } else {
466             fullURL = window.location.href.substr(0, window.location.href.lastIndexOf("/") + 1) + url;
467         }
468     }
470     // what's a window without a name. important for stacking and opener.
471     winname = (winname === "_blank" || !winname) ? dialogID() : winname;
473     // for small screens or request width is larger than viewport.
474     if (where.innerWidth <= 768) {
475         width = "modal-xl";
476     }
477     // Convert dialog size to percentages and/or css class.
478     var sizeChoices = ['modal-sm', 'modal-md', 'modal-mlg', 'modal-lg', 'modal-xl', 'modal-full'];
479     if (Math.abs(width) > 0) {
480         width = Math.abs(width);
481         mWidth = (width / where.innerWidth * 100).toFixed(1) + '%';
482         msSize = '<style>.modal-custom-' + winname + ' {max-width:' + mWidth + ' !important;}</style>';
483         mSize = 'modal-custom' + winname;
484     } else if (jQuery.inArray(width, sizeChoices) !== -1) {
485         mSize = width; // is a modal class
486     } else {
487         msSize = '<style>.modal-custom-' + winname + ' {max-width:35% !important;}</style>'; // standard B.S. modal default (modal-md)
488     }
489     // leave below for legacy
490     if (mSize === 'modal-sm') {
491         msSize = '<style>.modal-custom-' + winname + ' {max-width:25% !important;}</style>';
492     } else if (mSize === 'modal-md') {
493         msSize = '<style>.modal-custom-' + winname + ' {max-width:40% !important;}</style>';
494     } else if (mSize === 'modal-mlg') {
495         msSize = '<style>.modal-custom-' + winname + ' {max-width:55% !important;}</style>';
496     } else if (mSize === 'modal-lg') {
497         msSize = '<style>.modal-custom-' + winname + ' {max-width:75% !important;}</style>';
498     } else if (mSize === 'modal-xl') {
499         msSize = '<style>.modal-custom-' + winname + ' {max-width:92% !important;}</style>';
500     } else if (mSize === 'modal-full') {
501         msSize = '<style>.modal-custom-' + winname + ' {max-width:97% !important;}</style>';
502     }
503     mSize = 'modal-custom-' + winname;
505     // Initial responsive height.
506     let vpht = where.innerHeight;
507     if (height <= 300) {
508         height = 300;
509     }
510     mHeight = height > 0 ? (height / vpht * 100).toFixed(1) + 'vh' : '';
512     // Build modal template. For now !title = !header and modal full height.
513     var mTitle = title > "" ? '<h5 class=modal-title>' + title + '</h5>' : '';
515     var waitHtml =
516         '<div class="loadProgress text-center">' +
517         '<span class="fa fa-circle-notch fa-spin fa-3x text-primary"></span>' +
518         '</div>';
520     var headerhtml =
521         ('<div class="modal-header">%title%<button type="button" class="close" data-dismiss="modal">' +
522             '&times;</button></div>').replace('%title%', mTitle);
524     var frameHtml =
525         ('<iframe id="modalframe" class="w-100 h-100 modalIframe" name="%winname%" %url% frameborder=0></iframe>').replace('%winname%', winname).replace('%url%', fullURL ? 'src=' + fullURL : '');
527     var contentStyles = ('style="height:%initHeight%; max-height: 94vh"').replace('%initHeight%', opts.sizeHeight !== 'full' ? mHeight : '85vh');
529     var altClose = '<div class="closeDlgIframe" data-dismiss="modal" ></div>';
531     var mhtml =
532         ('<div id="%id%" class="modal fade dialogModal" tabindex="-1" role="dialog">%sizeStyle%' +
533             '<style>.drag-resize {touch-action:none;user-select:none;}</style>' +
534             '<div %dialogId% class="modal-dialog %drag-action% %sizeClass%" role="dialog">' +
535             '<div class="modal-content %resize-action%" %contentStyles%>' + '%head%' + '%altclose%' + '%wait%' +
536             '<div class="modal-body px-1">' + '%body%' + '</div></div></div></div>').replace('%id%', winname).replace('%sizeStyle%', msSize ? msSize : '').replace('%dialogId%', opts.dialogId ? ('id=' + opts.dialogId + '"') : '').replace('%sizeClass%', mSize ? mSize : '').replace('%head%', mTitle !== '' ? headerhtml : '').replace('%altclose%', mTitle === '' ? altClose : '').replace('%drag-action%', (opts.allowDrag) ? 'drag-action' : '').replace('%resize-action%', (opts.allowResize) ? 'resize-action' : '').replace('%wait%', '').replace('%contentStyles%', contentStyles).replace('%body%', opts.type === 'iframe' ? frameHtml : '');
538     // Write modal template.
539     dlgContainer = where.jQuery(mhtml);
540     dlgContainer.attr("name", winname);
542     // No url and just iframe content
543     if (opts.frameContent && opts.type === 'iframe') {
544         var ipath = 'data:text/html,' + encodeURIComponent(opts.frameContent);
545         dlgContainer.find("iframe[name='" + winname + "']").attr("src", ipath);
546     }
548     if (opts.buttons) {
549         dlgContainer.find('.modal-content').append(buildFooter());
550     }
551     // Ajax setup
552     if (opts.type === 'alert') {
553         dlgContainer.find('.modal-body').html(opts.html);
554     }
555     if (opts.type === 'confirm') {
556         dlgContainer.find('.modal-body').html(opts.html);
557     }
558     if (opts.type !== 'iframe' && opts.type !== 'alert' && opts.type !== 'confirm') {
559         var params = {
560             async: opts.async,
561             method: opts.type || '', // if empty and has data object, then post else get.
562             content: opts.data || opts.html, // ajax loads fetched content.
563             url: opts.url || fullURL,
564             dataType: opts.dataType || '' // xml/json/text etc.
565         };
567         dialogAjax(params, dlgContainer, opts);
568     }
570     // let opener array know about us.
571     top.set_opener(winname, window);
573     // Write the completed template to calling document or 'where' window.
574     where.jQuery("body").append(dlgContainer);
576     // We promised
577     return new Promise((resolve, reject) => {
578         jQuery(function () {
579             // DOM Ready. Handle events and cleanup.
580             if (opts.type === 'iframe') {
581                 var modalwin = where.jQuery('body').find("[name='" + winname + "']");
582                 jQuery('div.modal-dialog', modalwin).css({'margin': "0.75rem auto auto"});
583                 modalwin.on('load', function (e) {
584                     setTimeout(function () {
585                         if (opts.sizeHeight === 'auto' && opts.type === 'iframe') {
586                             SizeModaliFrame(e, height);
587                         } else if (opts.sizeHeight === 'fixed') {
588                             sizing(e, height);
589                         } else {
590                             sizing(e, height); // must be full height of container
591                         }
592                     }, 800);
593                 });
594             } else {
595                 var modalwin = where.jQuery('body').find("[name='" + winname + "']");
596                 jQuery('div.modal-dialog', modalwin).css({'margin': '15px auto auto'});
597                 modalwin.on('show.bs.modal', function (e) {
598                     setTimeout(function () {
599                         sizing(e, height);
600                     }, 800);
601                 });
602             }
603             if (opts.resolvePromiseOn === 'confirm') {
604                 jQuery("#confirmYes").on('click', function (e) {
605                     resolve(true);
606                 });
607                 jQuery("#confirmNo").on('click', function (e) {
608                     resolve(false);
609                 });
610             }
611             // events chain.
612             dlgContainer.on('show.bs.modal', function () {
613                 if (opts.allowResize || opts.allowDrag) {
614                     initDragResize(where.document, where.document);
615                 }
617                 if (opts.resolvePromiseOn === 'show') {
618                     resolve(dlgContainer);
619                 }
620             }).on('shown.bs.modal', function () {
621                 // Remove waitHtml spinner/loader etc.
622                 jQuery(this).parent().find('div.loadProgress').fadeOut(function () {
623                     jQuery(this).remove();
624                 });
625                 dlgContainer.modal('handleUpdate'); // allow for scroll bar
627                 if (opts.resolvePromiseOn === 'shown') {
628                     resolve(dlgContainer);
629                 }
630             }).on('hidden.bs.modal', function (e) {
631                 // clear cursor
632                 e.target.style.cursor = "pointer";
633                 // remove our dialog
634                 jQuery(this).remove();
635                 // now we can run functions in our window.
636                 if (opts.onClosed) {
637                     console.log('Doing onClosed:[' + opts.onClosed + ']');
638                     if (opts.onClosed === 'reload') {
639                         window.location.reload();
640                     } else {
641                         window[opts.onClosed]();
642                     }
643                 }
644                 if (opts.callBack.call) {
645                     console.log('Doing callBack:[' + opts.callBack.call + '|' + opts.callBack.args + ']');
646                     if (opts.callBack.call === 'reload') {
647                         window.location.reload();
648                     } else {
649                         window[opts.callBack.call](opts.callBack.args);
650                     }
651                 }
653                 if (opts.resolvePromiseOn == 'close') {
654                     resolve(dlgContainer);
655                 }
656             });
658             // define local dialog close() function. openers scope
659             window.dlgCloseAjax = function (calling, args) {
660                 if (calling) {
661                     opts.callBack = {call: calling, args: args};
662                 }
663                 dlgContainer.modal('hide'); // important to clean up in only one place, hide event....
664                 return false;
665             };
667             // define local callback function. Set with opener or from opener, will exe on hide.
668             window.dlgSetCallBack = function (calling, args) {
669                 opts.callBack = {call: calling, args: args};
670                 return false;
671             };
673             // in residents dialog scope
674             where.setCallBack = function (calling, args) {
675                 opts.callBack = {call: calling, args: args};
676                 return true;
677             };
679             where.getOpener = function () {
680                 return where;
681             };
682             // dialog is completely built and events set
683             // this is default returning our dialog container reference.
684             if (opts.resolvePromiseOn == 'init') {
685                 resolve(dlgContainer);
686             }
687             // Finally Show Dialog after DOM settles
688             dlgContainer.modal({backdrop: 'static', keyboard: true}, 'show');
689         }); // end events
690     }); /* Returning Promise */
692     // Ajax call with promise via dialog
693     function dialogAjax(data, $dialog, opts) {
694         var params = {
695             async: data.async,
696             method: data.method || '',
697             data: data.content,
698             url: data.url,
699             dataType: data.dataType || 'html'
700         };
702         if (data.url) {
703             jQuery.extend(params, data);
704         }
706         jQuery.ajax(params).done(aOkay).fail(oops);
708         return true;
710         function aOkay(html) {
711             opts.ajax = true;
712             $dialog.find('.modal-body').html(data.success ? data.success(html) : html);
714             return true;
715         }
717         function oops(r, s) {
718             var msg = data.error ?
719                 data.error(r, s, params) :
720                 '<div class="alert alert-danger">' +
721                 '<strong>XHR Failed:</strong> [ ' + params.url + '].' + '</div>';
723             $dialog.find('.modal-body').html(msg);
725             return false;
726         }
727     }
729     function buildFooter() {
730         if (opts.buttons === false) {
731             return '';
732         }
733         var oFoot = jQuery('<div>').addClass('modal-footer').prop('id', 'oefooter');
734         if (opts.buttons) {
735             for (var i = 0, k = opts.buttons.length; i < k; i++) {
736                 var btnOp = opts.buttons[i];
737                 if (typeof btnOp.class !== 'undefined') {
738                     btnOp.class = btnOp.class.replace(/default/gi, 'secondary');
739                     var btn = jQuery('<button>').addClass('btn ' + (btnOp.class || 'btn-primary'));
740                 } else { // legacy
741                     btnOp.style = btnOp.style.replace(/default/gi, 'secondary');
742                     var btn = jQuery('<button>').addClass('btn btn-' + (btnOp.style || 'primary'));
743                     btnOp.style = "";
744                 }
745                 for (var index in btnOp) {
746                     if (btnOp.hasOwnProperty(index)) {
747                         switch (index) {
748                             case 'close':
749                                 //add close event
750                                 if (btnOp[index]) {
751                                     btn.attr('data-dismiss', 'modal');
752                                 }
753                                 break;
754                             case 'click':
755                                 //binds button to click event of fn defined in calling document/form
756                                 var fn = btnOp.click.bind(dlgContainer.find('.modal-content'));
757                                 btn.click(fn);
758                                 break;
759                             case 'text':
760                                 btn.html(btnOp[index]);
761                                 break;
762                             case 'class':
763                                 break;
764                             default:
765                                 //all other possible HTML attributes to button element
766                                 // name, id etc
767                                 btn.attr(index, btnOp[index]);
768                         }
769                     }
770                 }
772                 oFoot.append(btn);
773             }
774         }
775         return oFoot; // jquery object of modal footer.
776     }
778     // dynamic sizing - special case for full height
779     function sizing(e, height) {
780         let viewPortHt = 0;
781         if (opts.sizeHeight === 'auto') {
782             dlgContainer.find('div.modal-body').css({'overflow-y': 'auto'});
783             // let BS determine height for alerts etc
784             return;
785         }
786         let $idoc = jQuery(e.currentTarget);
787         viewPortHt = Math.max(window.document.documentElement.clientHeight, window.innerHeight || 0);
788         let frameContentHt = opts.sizeHeight === 'full' ? viewPortHt : height;
789         frameContentHt = frameContentHt >= viewPortHt ? viewPortHt : frameContentHt;
790         size = (frameContentHt / viewPortHt * 100).toFixed(2);
791         size = size + 'vh';
792         dlgContainer.find('div.modal-content').css({'height': size});
793         if (opts.type === 'iframe') {
794             dlgContainer.find('div.modal-body').css({'overflow-y': 'hidden'});
795         } else {
796             dlgContainer.find('div.modal-body').css({'overflow-y': 'auto'});
797         }
800         return size;
801     }
803     // sizing for modals with iframes
804     function SizeModaliFrame(e, minSize) {
805         let viewPortHt = where.window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
806         let frameContentHt = 0;
807         let idoc = null;
808         try {
809             idoc = e.currentTarget.contentDocument ? e.currentTarget.contentDocument : e.currentTarget.contentWindow.document;
810             jQuery(e.currentTarget).parents('div.modal-content').css({'height': 0});
811             frameContentHt = Math.max(jQuery(idoc).height(), idoc.body.offsetHeight) + 40;
812         } catch(err){
813             frameContentHt = minSize + 40;
814         }
815         frameContentHt = frameContentHt <= minSize ? minSize : frameContentHt;
816         frameContentHt = frameContentHt >= viewPortHt ? viewPortHt : frameContentHt;
817         size = (frameContentHt / viewPortHt * 100).toFixed(1);
818         size = size + 'vh';
819         jQuery(e.currentTarget).parents('div.modal-content').css({'height': size});
821         return size;
822     }