some portal work
[openemr.git] / library / dialog.js
blobf7884e8004200fa5c063f1e5b72488526ac09bdf
1 // Copyright (C) 2005 Rod Roark <rod@sunsetsystems.com>
2 // Copyright (C) 2018-2019 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.closeAjax = closeAjax;
17         return root;
19         function ajax(data) {
20             let opts = {
21                 buttons: data.buttons,
22                 sizeHeight: data.sizeHeight || '',
23                 type: data.type,
24                 data: data.data,
25                 url: data.url,
26                 dataType: data.dataType || '' // xml/json/text etc.
27             };
29             let title = data.title;
31             return dlgopen('', '', data.size, 250, '', title, opts);
32         }
34         function alert(data, title) {
35             title = title ? title : 'Alert';
36             let alertTitle = '<i class="fa fa-warning alert-danger"></i>&nbsp;<span>' + title + '</span>';
37             return dlgopen('', '', 675, 250, '', alertTitle, {
38                 buttons: [
39                     {text: '<i class="fa fa-thumbs-up">&nbsp;OK</i>', close: true, style: 'default'}
40                 ],
41                 type: 'Alert',
42                 html: '<blockquote class="blockquote">' + data + '</blockquote>'
43             });
44         }
46         function closeAjax() {
47             dlgCloseAjax();
48         }
49     });
51     if (typeof window.xl !== 'function') {
52         let utilfn = top.webroot_url + '/library/js/utility.js';
53         let load = async () => {
54             await includeScript(utilfn, false, 'script');
55         };
56         load().then(rtn => {
57             console.log('Utilities Unavailable! loading:[ ' + utilfn + ' ] For: [ ' + location + ' ]');
58         });
59     }
60 }(typeof define == 'function' && define.amd ?
61     define :
62     function (args, mName) {
63         this.dialog = typeof module != 'undefined' && module.exports ?
64             mName(require(args[0], {}), module.exports) :
65             mName(window.$);
66     }));
69 // open a new cascaded window
70 function cascwin(url, winname, width, height, options) {
71     var mywin = window.parent ? window.parent : window;
72     var newx = 25, newy = 25;
73     if (!isNaN(mywin.screenX)) {
74         newx += mywin.screenX;
75         newy += mywin.screenY;
76     } else if (!isNaN(mywin.screenLeft)) {
77         newx += mywin.screenLeft;
78         newy += mywin.screenTop;
79     }
80     if ((newx + width) > screen.width || (newy + height) > screen.height) {
81         newx = 0;
82         newy = 0;
83     }
84     top.restoreSession();
86     // MS IE version detection taken from
87     // http://msdn2.microsoft.com/en-us/library/ms537509.aspx
88     // to adjust the height of this box for IE only -- JRM
89     if (navigator.appName == 'Microsoft Internet Explorer') {
90         var ua = navigator.userAgent;
91         var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
92         if (re.exec(ua) != null)
93             rv = parseFloat(RegExp.$1); // this holds the version number
94         height = height + 28;
95     }
97     retval = window.open(url, winname, options +
98         ",width=" + width + ",height=" + height +
99         ",left=" + newx + ",top=" + newy +
100         ",screenX=" + newx + ",screenY=" + newy);
102     return retval;
105 // recursive window focus-event grabber
106 function grabfocus(w) {
107     for (var i = 0; i < w.frames.length; ++i) grabfocus(w.frames[i]);
108     w.onfocus = top.imfocused;
111 // Call this when a "modal" windowed dialog is desired.
112 // Note that the below function is free standing for either
113 // ui's.Use dlgopen() for responsive b.s modal dialogs.
114 // Can now use anywhere to cascade natives...12/1/17 sjp
116 function dlgOpenWindow(url, winname, width, height) {
117     if (top.modaldialog && !top.modaldialog.closed) {
118         if (window.focus) top.modaldialog.focus();
119         if (top.modaldialog.confirm(top.oemr_dialog_close_msg)) {
120             top.modaldialog.close();
121             top.modaldialog = null;
122         } else {
123             return false;
124         }
125     }
126     top.modaldialog = cascwin(url, winname, width, height,
127         "resizable=1,scrollbars=1,location=0,toolbar=0");
129     return false;
132 // This is called from del_related() which in turn is invoked by find_code_dynamic.php.
133 // Deletes the specified codetype:code from the indicated input text element.
134 function my_del_related(s, elem, usetitle) {
135     if (!s) {
136         // Deleting everything.
137         elem.value = '';
138         if (usetitle) {
139             elem.title = '';
140         }
141         return;
142     }
143     // Convert the codes and their descriptions to arrays for easy manipulation.
144     var acodes = elem.value.split(';');
145     var i = acodes.indexOf(s);
146     if (i < 0) {
147         return; // not found, should not happen
148     }
149     // Delete the indicated code and description and convert back to strings.
150     acodes.splice(i, 1);
151     elem.value = acodes.join(';');
152     if (usetitle) {
153         var atitles = elem.title.split(';');
154         atitles.splice(i, 1);
155         elem.title = atitles.join(';');
156     }
159 function dialogID() {
160     function s4() {
161         return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
162     }
164     return s4() + s4() + s4() + s4() + s4() + +s4() + s4() + s4();
168 * function includeScript(url, async)
170 * @summary Dynamically include JS Scripts or Css.
172 * @param {string} url file location.
173 * @param {boolean} async true/false load asynchronous/synchronous.
174 * @param {string} 'script' | 'link'.
176 * */
177 function includeScript(url, async, type) {
178     try {
179         let rqit = new XMLHttpRequest();
180         if (type === "link") {
181             let headElement = document.getElementsByTagName("head")[0];
182             let newScriptElement = document.createElement("link")
183             newScriptElement.type = "text/css";
184             newScriptElement.rel = "stylesheet";
185             newScriptElement.href = url;
186             headElement.appendChild(newScriptElement);
187             console.log('Needed to load:[ ' + url + ' ] For: [ ' + location + ' ]');
188             return false;
189         }
191         rqit.open("GET", url, async); // false = synchronous.
192         rqit.send(null);
194         if (rqit.status === 200) {
195             if (type === "script") {
196                 let headElement = document.getElementsByTagName("head")[0];
197                 let newScriptElement = document.createElement("script");
198                 newScriptElement.type = "text/javascript";
199                 newScriptElement.text = rqit.responseText;
200                 headElement.appendChild(newScriptElement);
201                 console.log('Needed to load:[ ' + url + ' ] For: [ ' + location + ' ]');
202                 return false; // in case req comes from a submit form.
203             }
204         }
206         new Error("Failed to get URL:" + url);
208     } catch (e) {
209         throw e;
210     }
214 // test for and/or remove dependency.
215 function inDom(dependency, type, remove) {
216     let el = type;
217     let attr = type === 'script' ? 'src' : type === 'link' ? 'href' : 'none';
218     let all = document.getElementsByTagName(el);
219     for (let i = all.length; i > -1; i--) {
220         if (all[i] && all[i].getAttribute(attr) !== null && all[i].getAttribute(attr).indexOf(dependency) !== -1) {
221             if (remove) {
222                 all[i].parentNode.removeChild(all[i]);
223                 console.log("Removed from DOM: " + dependency);
224                 return true;
225             } else {
226                 return true;
227             }
228         }
229     }
230     return false;
233 // These functions may be called from scripts that may be out of scope with top so...
234 // if opener is tab then we need to be in tabs UI scope and while we're at it, let's bring webroot along...
236 if (typeof top.webroot_url === "undefined" && opener) {
237     if (typeof opener.top.webroot_url !== "undefined") {
238         top.webroot_url = opener.top.webroot_url;
239     }
241 // We'll need these if out of scope
243 if (typeof top.set_opener !== "function") {
244     var opener_list = [];
246     function set_opener(window, opener) {
247         top.opener_list[window] = opener;
248     }
250     function get_opener(window) {
251         return top.opener_list[window];
252     }
255 // universal alert popup message
256 if (typeof alertMsg !== "function") {
257     function alertMsg(message, timer = 5000, type = 'danger', size = '', persist = '') {
258         // example of get php to js variables.
259         let isPromise = top.jsFetchGlobals('alert');
260         isPromise.then(xl => {
261             $('#alert_box').remove();
262             let oHidden = '';
263             oHidden = !persist ? "hidden" : '';
264             let oSize = (size == 'lg') ? 'left:10%;width:80%;' : 'left:25%;width:50%;';
265             let style = "position:fixed;top:25%;" + oSize + " bottom:0;z-index:9999;";
266             $("body").prepend("<div class='container text-center' id='alert_box' style='" + style + "'></div>");
267             let mHtml = '<div id="alertmsg" class="alert alert-' + type + ' alert-dismissable">' +
268                 '<button type="button" class="btn btn-link ' + oHidden + '" id="dontShowAgain" data-dismiss="alert">' +
269                 xl.alert.gotIt + '&nbsp;<i class="fa fa-thumbs-up"></i></button>' +
270                 '<h4 class="alert-heading text-center">' + xl.alert.title + '!</h4><hr>' + '<p style="color:#000;">' + message + '</p>' +
271                 '<button type="button" class="pull-right btn btn-link" data-dismiss="alert">' + xl.alert.dismiss + '</button></br></div>';
272             $('#alert_box').append(mHtml);
273             $('#alertmsg').on('closed.bs.alert', function () {
274                 clearTimeout(AlertMsg);
275                 $('#alert_box').remove();
276                 return false;
277             });
278             $('#dontShowAgain').on('click', function (e) {
279                 persistUserOption(persist, 1);
280             });
281             let AlertMsg = setTimeout(function () {
282                 $('#alertmsg').fadeOut(800, function () {
283                     $('#alert_box').remove();
284                 });
285             }, timer);
286         }).catch(error => {
287             console.log(error.message)
288         });
289     }
291     const persistUserOption = function (option, value) {
292         return $.ajax({
293             url: top.webroot_url + "/library/ajax/user_settings.php",
294             type: 'post',
295             contentType: 'application/x-www-form-urlencoded',
296             data: {
297                 csrf_token_form: top.csrf_token_js,
298                 target: option,
299                 setting: value
300             },
301             beforeSend: function () {
302                 top.restoreSession();
303             },
304             error: function (jqxhr, status, errorThrown) {
305                 console.log(errorThrown);
306             }
307         });
308     };
312 // Test if supporting dialog callbacks and close dependencies are in scope.
313 // This is useful when opening and closing the dialog is in the same scope. Still use include_opener.js
314 // in script that will close a dialog that is not in the same scope dlgopen was used
315 // or use parent.dlgclose() if known decendent.
316 // dlgopen() will always have a name whether assigned by dev or created by function.
317 // Callback, onClosed and button clicks are still available either way.
318 // For a callback on close use: dlgclose(functionName, farg1, farg2 ...) which becomes: functionName(farg1,farg2, etc)
320 if (typeof dlgclose !== "function") {
321     if (!opener) {
322         opener = window;
323     }
325     function dlgclose(call, args) {
326         var frameName = window.name;
327         var wframe = top;
328         if (frameName === '') {
329             // try to find dialog. dialogModal is embedded dialog class
330             // It has to be here somewhere.
331             frameName = $(".dialogModal").attr('id');
332             if (!frameName) {
333                 frameName = parent.$(".dialogModal").attr('id');
334                 if (!frameName) {
335                     console.log("Unable to find dialog.");
336                     return false;
337                 }
338             }
339         }
340         var dialogModal = top.$('div#' + frameName);
342         var removeFrame = dialogModal.find("iframe[name='" + frameName + "']");
343         if (removeFrame.length > 0) {
344             removeFrame.remove();
345         }
347         if (dialogModal.length > 0) {
348             if (call) {
349                 wframe.setCallBack(call, args); // sets/creates callback function in dialogs scope.
350             }
351             dialogModal.modal('hide');
352         }
353     };
357 * function dlgopen(url, winname, width, height, forceNewWindow, title, opts)
359 * @summary Stackable, resizable and draggable responsive ajax/iframe dialog modal.
361 * @param {url} string Content location.
362 * @param {String} winname If set becomes modal id and/or iframes name. Or, one is created/assigned(iframes).
363 * @param {Number| String} width|modalSize(modal-xl) For sizing: an number will be converted to a percentage of view port width.
364 * @param {Number} height Initial minimum height. For iframe auto resize starts at this height.
365 * @param {boolean} forceNewWindow Force using a native window.
366 * @param {String} title If exist then header with title is created otherwise no header and content only.
367 * @param {Object} opts Dialogs options.
368 * @returns {Object} dialog object reference.
369 * */
370 const dlgopen = (url, winname, width, height, forceNewWindow, title, opts) => {
371     // First things first...
372     top.restoreSession();
373     // A matter of Legacy
374     if (forceNewWindow) {
375         return dlgOpenWindow(url, winname, width, height);
376     }
378     // wait for DOM then check dependencies needed to run this feature.
379     // dependency duration is while 'this' is in scope, temporary...
380     // seldom will this get used as more of U.I is moved to Bootstrap
381     // but better to continue than stop because of a dependency...
382     //
383     let jqurl = top.webroot_url + '/public/assets/jquery/dist/jquery.min.js';
384     if (typeof jQuery === 'undefined') {
385         includeScript(jqurl, false, 'script'); // true is async
386     }
387     jQuery(function () {
388         // Check for dependencies we will need.
389         // webroot_url is a global defined in main_screen.php or main.php.
390         let bscss = top.webroot_url + '/public/assets/bootstrap/dist/css/bootstrap.min.css';
391         let bscssRtl = top.webroot_url + '/public/assets/bootstrap-v4-rtl/dist/css/bootstrap-rtl.min.css';
392         let bsurl = top.webroot_url + '/public/assets/bootstrap/dist/js/bootstrap.bundle.min.js';
394         let version = jQuery.fn.jquery.split(' ')[0].split('.');
395         if ((version[0] < 2 && version[1] < 9) || (version[0] === 1 && version[1] === 9 && version[2] < 1)) {
396             inDom('jquery-min', 'script', true);
397             includeScript(jqurl, false, 'script');
398             console.log('Replacing jQuery version:[ ' + version + ' ]');
399         }
400         if (!inDom('bootstrap.min.css', 'link', false)) {
401             includeScript(bscss, false, 'link');
402             if (top.jsLanguageDirection === 'rtl') {
403                 includeScript(bscssRtl, false, 'link');
404             }
405         }
406         if (typeof jQuery.fn.modal === 'undefined') {
407             if (!inDom('bootstrap.bundle.min.js', 'script', false))
408                 includeScript(bsurl, false, 'script');
409         }
410     });
412     // onward
413     var opts_defaults = {
414         type: 'iframe', // POST, GET (ajax) or iframe
415         async: true,
416         frameContent: "", // for iframe embedded content
417         html: "", // content for alerts, comfirm etc ajax
418         allowDrag: true,
419         allowResize: true,
420         sizeHeight: 'auto', // 'full' will use as much height as allowed
421         // use is onClosed: fnName ... args not supported however, onClosed: 'reload' is auto defined and requires no function to be created.
422         onClosed: false,
423         callBack: false // use {call: 'functionName, args: args, args} if known or use dlgclose.
424     };
426     if (!opts) {
427         opts = {};
428     }
429     opts = jQuery.extend({}, opts_defaults, opts);
430     opts.type = opts.type ? opts.type.toLowerCase() : '';
432     var mHeight, mWidth, mSize, msSize, dlgContainer, fullURL, where; // a growing list...
434     where = opts.type === 'iframe' ? top : window;
436     // get url straight...
437     fullURL = "";
438     if (opts.url) {
439         url = opts.url;
440     }
441     if (url) {
442         if (url[0] === "/") {
443             fullURL = url
444         } else {
445             fullURL = window.location.href.substr(0, window.location.href.lastIndexOf("/") + 1) + url;
446         }
447     }
449     // what's a window without a name. important for stacking and opener.
450     winname = (winname === "_blank" || !winname) ? dialogID() : winname;
452     // for small screens or request width is larger than viewport.
453     if (where.innerWidth <= 768) {
454         width = "modal-xl";
455     }
456     // Convert dialog size to percentages and/or css class.
457     var sizeChoices = ['modal-sm', 'modal-md', 'modal-mlg', 'modal-lg', 'modal-xl'];
458     if (Math.abs(width) > 0) {
459         width = Math.abs(width);
460         mWidth = (width / where.innerWidth * 100).toFixed(1) + '%';
461         msSize = '<style>.modal-custom-' + winname + ' {max-width:' + mWidth + ' !important;}</style>';
462         mSize = 'modal-custom' + winname;
463     } else if (jQuery.inArray(width, sizeChoices) !== -1) {
464         mSize = width; // is a modal class
465     } else {
466         msSize = '<style>.modal-custom-' + winname + ' {max-width:35% !important;}</style>'; // standard B.S. modal default (modal-md)
467     }
468     // leave below for legacy
469     if (mSize === 'modal-sm') {
470         msSize = '<style>.modal-custom-' + winname + ' {max-width:25% !important;}</style>';
471     } else if (mSize === 'modal-md') {
472         msSize = '<style>.modal-custom-' + winname + ' {max-width:40% !important;}</style>';
473     } else if (mSize === 'modal-mlg') {
474         msSize = '<style>.modal-custom-' + winname + ' {max-width:55% !important;}</style>';
475     } else if (mSize === 'modal-lg') {
476         msSize = '<style>.modal-custom-' + winname + ' {max-width:75% !important;}</style>';
477     } else if (mSize === 'modal-xl') {
478         msSize = '<style>.modal-custom-' + winname + ' {max-width:92% !important;}</style>';
479     }
480     mSize = 'modal-custom-' + winname;
482     // Initial responsive height.
483     var vpht = where.innerHeight;
484     mHeight = height > 0 ? (height / vpht * 100).toFixed(1) + 'vh' : '';
486     // Build modal template. For now !title = !header and modal full height.
487     var mTitle = title > "" ? '<h5 class=modal-title>' + title + '</h5>' : '';
489     var waitHtml =
490         '<div class="loadProgress text-center">' +
491         '<span class="fa fa-circle-o-notch fa-spin fa-3x text-primary"></span>' +
492         '</div>';
494     var headerhtml =
495         ('<div class=modal-header>%title%<button type=button class="close" data-dismiss=modal>' +
496             '&times;</button></div>').replace('%title%', mTitle);
498     var frameHtml =
499         ('<iframe id="modalframe" class="w-100 h-100 modalIframe" name="%winname%" %url% frameborder=0></iframe>')
500         .replace('%winname%', winname).replace('%url%', fullURL ? 'src=' + fullURL : '');
502     var bodyStyles = (' style="overflow-y:auto;height:%initHeight%;max-height:92vh;"')
503         .replace('%initHeight%', opts.sizeHeight !== 'full' ? mHeight : '80vh');
505     var altClose = '<div class="closeDlgIframe" data-dismiss="modal" ></div>';
507     const position = { x: 0, y: 0 };
509     var mhtml =
510         ('<div id="%id%" class="modal fade dialogModal" tabindex="-1" role="dialog">%sizeStyle%' +
511             '<style>.drag-resize {touch-action:none;user-select:none;}</style>' +
512             '<div %dialogId% class="modal-dialog %drag-action% %sizeClass%" role="document">' +
513             '<div class="modal-content %resize-action%">' + '%head%' + '%altclose%' + '%wait%' +
514             '<div class="modal-body px-1" %bodyStyles%>' + '%body%' + '</div></div></div></div>')
515         .replace('%id%', winname)
516         .replace('%sizeStyle%', msSize ? msSize : '')
517         .replace('%dialogId%', opts.dialogId ? ('id=' + opts.dialogId + '"') : '')
518         .replace('%sizeClass%', mSize ? mSize : '')
519         .replace('%head%', mTitle !== '' ? headerhtml : '')
520         .replace('%altclose%', mTitle === '' ? altClose : '')
521         .replace('%drag-action%', (opts.allowDrag) ? 'drag-action' : '')
522         .replace('%resize-action%', (opts.allowResize) ? 'resize-action' : '')
523         .replace('%wait%', '')
524         .replace('%bodyStyles%', bodyStyles)
525         .replace('%body%', opts.type === 'iframe' ? frameHtml : '');
527     // Write modal template.
528     dlgContainer = where.jQuery(mhtml);
529     dlgContainer.attr("name", winname);
531     // No url and just iframe content
532     if (opts.frameContent && opts.type === 'iframe') {
533         var ipath = 'data:text/html,' + encodeURIComponent(opts.frameContent);
534         dlgContainer.find("iframe[name='" + winname + "']").attr("src", ipath);
535     }
537     if (opts.buttons) {
538         dlgContainer.find('.modal-content').append(buildFooter());
539     }
540     // Ajax setup
541     if (opts.type === 'alert') {
542         dlgContainer.find('.modal-body').html(opts.html);
543     }
544     if (opts.type !== 'iframe' && opts.type !== 'alert') {
545         var params = {
546             async: opts.async,
547             method: opts.type || '', // if empty and has data object, then post else get.
548             content: opts.data || opts.html, // ajax loads fetched content.
549             url: opts.url || fullURL,
550             dataType: opts.dataType || '' // xml/json/text etc.
551         };
553         dialogAjax(params, dlgContainer, opts);
554     }
556     // let opener array know about us.
557     top.set_opener(winname, window);
559     // Write the completed template to calling document or 'where' window.
560     where.jQuery("body").append(dlgContainer);
562     // We promised
563     return new Promise((resolve, reject) => {
564         jQuery(function () {
565             // DOM Ready. Handle events and cleanup.
566             if (opts.type === 'iframe') {
567                 var modalwin = where.jQuery('body').find("[name='" + winname + "']");
568                 jQuery('div.modal-dialog', modalwin).css({'margin': '15px auto'});
569                 modalwin.on('load', function (e) {
570                     setTimeout(function () {
571                         if (opts.sizeHeight === 'auto') {
572                             SizeModaliFrame(e, height);
573                         } else if (opts.sizeHeight === 'fixed') {
574                             sizing(e, height);
575                         } else {
576                             sizing(e, height); // must be full height of container
577                         }
578                     }, 800);
579                 });
580             }
582             // events chain.
583             dlgContainer.on('show.bs.modal', function () {
584                 if (opts.allowResize || opts.allowDrag) {
585                     initDragResize(document, where.document);
586                 }
587             }).on('shown.bs.modal', function () {
588                 // Remove waitHtml spinner/loader etc.
589                 jQuery(this).parent().find('div.loadProgress').fadeOut(function () {
590                     jQuery(this).remove();
591                 });
592                 dlgContainer.modal('handleUpdate'); // allow for scroll bar
593             }).on('hidden.bs.modal', function (e) {
594                 // remove our dialog
595                 jQuery(this).remove();
596                 // now we can run functions in our window.
597                 if (opts.onClosed) {
598                     console.log('Doing onClosed:[' + opts.onClosed + ']');
599                     if (opts.onClosed === 'reload') {
600                         window.location.reload();
601                     } else {
602                         window[opts.onClosed]();
603                     }
604                 }
605                 if (opts.callBack.call) {
606                     console.log('Doing callBack:[' + opts.callBack.call + '|' + opts.callBack.args + ']');
607                     if (opts.callBack.call === 'reload') {
608                         window.location.reload();
609                     } else {
610                         window[opts.callBack.call](opts.callBack.args);
611                     }
612                 }
613                 // We resolve on modal closing so we can run after action items.
614                 // Todo: Move our closed and callback functions to new library function.
615                 // then() continue promise chain back to calling script.
616                 //
617                 resolve(true);
619             }).modal({backdrop: 'static', keyboard: true}, 'show');// Show Modal
621             // define local dialog close() function. openers scope
622             window.dlgCloseAjax = function (calling, args) {
623                 if (calling) {
624                     opts.callBack = {call: calling, args: args};
625                 }
626                 dlgContainer.modal('hide'); // important to clean up in only one place, hide event....
627                 return false;
628             };
630             // define local callback function. Set with opener or from opener, will exe on hide.
631             window.dlgSetCallBack = function (calling, args) {
632                 opts.callBack = {call: calling, args: args};
633                 return false;
634             };
636             // in residents dialog scope
637             where.setCallBack = function (calling, args) {
638                 opts.callBack = {call: calling, args: args};
639                 return true;
640             };
642             where.getOpener = function () {
643                 return where;
644             };
646             // Return the dialog ref. looking towards deferring...
647             return dlgContainer;
649         }); // end events
650     }); /* promise */
652     // Ajax call with promise
653     function dialogAjax(data, $dialog, opts) {
654         var params = {
655             async: data.async,
656             method: data.method || '',
657             data: data.content,
658             url: data.url,
659             dataType: data.dataType || 'html'
660         };
662         if (data.url) {
663             jQuery.extend(params, data);
664         }
666         jQuery.ajax(params).done(aOkay).fail(oops);
668         return true;
670         function aOkay(html) {
671             opts.ajax = true;
672             $dialog.find('.modal-body').html(data.success ? data.success(html) : html);
674             return true;
675         }
677         function oops(r, s) {
678             var msg = data.error ?
679                 data.error(r, s, params) :
680                 '<div class="alert alert-danger">' +
681                 '<strong>XHR Failed:</strong> [ ' + params.url + '].' + '</div>';
683             $dialog.find('.modal-body').html(msg);
685             return false;
686         }
687     }
689     function buildFooter() {
690         if (opts.buttons === false) {
691             return '';
692         }
693         var oFoot = jQuery('<div>').addClass('modal-footer').prop('id', 'oefooter');
694         if (opts.buttons) {
695             for (var i = 0, k = opts.buttons.length; i < k; i++) {
696                 var btnOp = opts.buttons[i];
697                 if (typeof btnOp.class !== 'undefined') {
698                     btnOp.class = btnOp.class.replace(/default/gi, 'secondary');
699                     var btn = jQuery('<button>').addClass('btn ' + (btnOp.class || 'btn-primary'));
700                 } else { // legacy
701                     btnOp.style = btnOp.style.replace(/default/gi, 'secondary');
702                     var btn = jQuery('<button>').addClass('btn btn-' + (btnOp.style || 'primary'));
703                     btnOp.style = "";
704                 }
705                 for (var index in btnOp) {
706                     if (btnOp.hasOwnProperty(index)) {
707                         switch (index) {
708                             case 'close':
709                                 //add close event
710                                 if (btnOp[index]) {
711                                     btn.attr('data-dismiss', 'modal');
712                                 }
713                                 break;
714                             case 'click':
715                                 //binds button to click event of fn defined in calling document/form
716                                 var fn = btnOp.click.bind(dlgContainer.find('.modal-content'));
717                                 btn.click(fn);
718                                 break;
719                             case 'text':
720                                 btn.html(btnOp[index]);
721                                 break;
722                             case 'class':
723                                 break;
724                             default:
725                                 //all other possible HTML attributes to button element
726                                 // name, id etc
727                                 btn.attr(index, btnOp[index]);
728                         }
729                     }
730                 }
732                 oFoot.append(btn);
733             }
734         } else {
735             //if no buttons defined by user, add a standard close button.
736             oFoot.append('<button class="closeBtn btn btn-secondary" data-dismiss=modal type=button><i class="fa fa-times-circle"></i></button>');
737         }
739         return oFoot; // jquery object of modal footer.
740     }
742     // dynamic sizing - special case for full height - @todo use for fixed wt and ht
743     function sizing(e, height) {
744         let viewPortHt = 0;
745         let $idoc = jQuery(e.currentTarget);
746         viewPortHt = Math.max(top.window.document.documentElement.clientHeight, top.window.innerHeight || 0);
747         viewPortWt = Math.max(top.window.document.documentElement.clientWidth, top.window.innerWidth || 0);
748         let frameContentHt = opts.sizeHeight === 'full' ? viewPortHt : height;
749         frameContentHt = frameContentHt > viewPortHt ? viewPortHt : frameContentHt;
750         let hasHeader = $idoc.parents('div.modal-content').find('div.modal-header').height() || 0;
751         let hasFooter = $idoc.parents('div.modal-content').find('div.modal-footer').height() || 0;
752         frameContentHt = frameContentHt - hasHeader - hasFooter;
753         size = (frameContentHt / viewPortHt * 100).toFixed(4);
754         let maxsize = hasHeader ? 90 : hasFooter ? 86.5 : 95.5;
755         maxsize = hasHeader && hasFooter ? 80 : maxsize;
756         maxsize = maxsize + 'vh';
757         size = size + 'vh';
758         $idoc.parents('div.modal-body').css({'height': size, 'max-height': maxsize, 'max-width': '96vw'});
760         return size;
761     }
763     // sizing for modals with iframes
764     function SizeModaliFrame(e, minSize) {
765         let viewPortHt;
766         let idoc = e.currentTarget.contentDocument ? e.currentTarget.contentDocument : e.currentTarget.contentWindow.document;
767         jQuery(e.currentTarget).parents('div.modal-content').height('');
768         jQuery(e.currentTarget).parent('div.modal-body').css({'height': 0});
769         viewPortHt = top.window.innerHeight || 0;
770         //minSize = 100;
771         let frameContentHt = Math.max(jQuery(idoc).height(), idoc.body.offsetHeight || 0) + 40;
772         frameContentHt = frameContentHt < minSize ? minSize : frameContentHt;
773         frameContentHt = frameContentHt > viewPortHt ? viewPortHt : frameContentHt;
774         let hasHeader = jQuery(e.currentTarget).parents('div.modal-content').find('div.modal-header').length;
775         let hasFooter = jQuery(e.currentTarget).parents('div.modal-content').find('div.modal-footer').length;
776         size = (frameContentHt / viewPortHt * 100).toFixed(4);
777         let maxsize = hasHeader ? 90 : hasFooter ? 87.5 : 96;
778         maxsize = hasHeader && hasFooter ? 80 : maxsize;
779         maxsize = maxsize + 'vh';
780         size = size + 'vh'; // will start the dialog as responsive. Any resize by user turns dialog to absolute positioning.
782         jQuery(e.currentTarget).parent('div.modal-body').css({'height': size, 'max-height': maxsize}); // Set final size. Width was previously set.
784         return size;
785     }