Fully responsive globals.php with vertical menu (#2460)
[openemr.git] / library / dialog.js
blobe6c4e7b2657c229a638486f31642a1b70e3c6fe1
1 // Copyright (C) 2005 Rod Roark <rod@sunsetsystems.com>
2 // Copyright (C) 2018 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 // open a new cascaded window
10 function cascwin(url, winname, width, height, options) {
11     var mywin = window.parent ? window.parent : window;
12     var newx = 25, newy = 25;
13     if (!isNaN(mywin.screenX)) {
14         newx += mywin.screenX;
15         newy += mywin.screenY;
16     } else if (!isNaN(mywin.screenLeft)) {
17         newx += mywin.screenLeft;
18         newy += mywin.screenTop;
19     }
20     if ((newx + width) > screen.width || (newy + height) > screen.height) {
21         newx = 0;
22         newy = 0;
23     }
24     top.restoreSession();
26     // MS IE version detection taken from
27     // http://msdn2.microsoft.com/en-us/library/ms537509.aspx
28     // to adjust the height of this box for IE only -- JRM
29     if (navigator.appName == 'Microsoft Internet Explorer') {
30         var ua = navigator.userAgent;
31         var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
32         if (re.exec(ua) != null)
33             rv = parseFloat(RegExp.$1); // this holds the version number
34         height = height + 28;
35     }
37     retval = window.open(url, winname, options +
38         ",width=" + width + ",height=" + height +
39         ",left=" + newx + ",top=" + newy +
40         ",screenX=" + newx + ",screenY=" + newy);
42     return retval;
45 // recursive window focus-event grabber
46 function grabfocus(w) {
47     for (var i = 0; i < w.frames.length; ++i) grabfocus(w.frames[i]);
48     w.onfocus = top.imfocused;
51 // Call this when a "modal" windowed dialog is desired.
52 // Note that the below function is free standing for either
53 // ui's.Use dlgopen() for responsive b.s modal dialogs.
54 // Can now use anywhere to cascade natives...12/1/17 sjp
56 function dlgOpenWindow(url, winname, width, height) {
57     if (top.modaldialog && !top.modaldialog.closed) {
58         if (window.focus) top.modaldialog.focus();
59         if (top.modaldialog.confirm(top.oemr_dialog_close_msg)) {
60             top.modaldialog.close();
61             top.modaldialog = null;
62         } else {
63             return false;
64         }
65     }
66     top.modaldialog = cascwin(url, winname, width, height,
67         "resizable=1,scrollbars=1,location=0,toolbar=0");
68     grabfocus(top);
69     return false;
72 // This is called from del_related() which in turn is invoked by find_code_dynamic.php.
73 // Deletes the specified codetype:code from the indicated input text element.
74 function my_del_related(s, elem, usetitle) {
75     if (!s) {
76         // Deleting everything.
77         elem.value = '';
78         if (usetitle) {
79             elem.title = '';
80         }
81         return;
82     }
83     // Convert the codes and their descriptions to arrays for easy manipulation.
84     var acodes = elem.value.split(';');
85     var i = acodes.indexOf(s);
86     if (i < 0) {
87         return; // not found, should not happen
88     }
89     // Delete the indicated code and description and convert back to strings.
90     acodes.splice(i, 1);
91     elem.value = acodes.join(';');
92     if (usetitle) {
93         var atitles = elem.title.split(';');
94         atitles.splice(i, 1);
95         elem.title = atitles.join(';');
96     }
99 function dialogID() {
100     function s4() {
101         return Math.floor((1 + Math.random()) * 0x10000)
102             .toString(16)
103             .substring(1);
104     }
106     return s4() + s4() + s4() + s4() + s4() + +s4() + s4() + s4();
110 * function includeScript(url, async)
112 * @summary Dynamically include JS Scripts or Css.
114 * @param {string} url file location.
115 * @param {boolean} async true/false load asynchronous/synchronous.
116 * @param {string} 'script' | 'link'.
118 * */
119 function includeScript(url, async, type) {
121     try {
122         let rqit = new XMLHttpRequest();
123         if (type === "link") {
124             let headElement = document.getElementsByTagName("head")[0];
125             let newScriptElement = document.createElement("link")
126             newScriptElement.type = "text/css";
127             newScriptElement.rel = "stylesheet";
128             newScriptElement.href = url;
129             headElement.appendChild(newScriptElement);
130             console.log('Needed to load:[ ' + url + ' ] For: [ ' + location + ' ]');
131             return false;
132         }
134         rqit.open("GET", url, async); // false = synchronous.
135         rqit.send(null);
137         if (rqit.status === 200) {
138             if (type === "script") {
139                 let headElement = document.getElementsByTagName("head")[0];
140                 let newScriptElement = document.createElement("script");
141                 newScriptElement.type = "text/javascript";
142                 newScriptElement.text = rqit.responseText;
143                 headElement.appendChild(newScriptElement);
144                 console.log('Needed to load:[ ' + url + ' ] For: [ ' + location + ' ]');
145                 return false; // in case req comes from a submit form.
146             }
147         }
149         throw new Error("Failed to get URL:" + url);
151     }
152     catch (e) {
153         throw e;
154     }
158 // test for and/or remove dependency.
159 function inDom(dependency, type, remove) {
160     let el = type;
161     let attr = type === 'script' ? 'src' : type === 'link' ? 'href' : 'none';
162     let all = document.getElementsByTagName(el)
163     for (let i = all.length; i > -1; i--) {
164         if (all[i] && all[i].getAttribute(attr) != null && all[i].getAttribute(attr).indexOf(dependency) != -1) {
165             if (remove) {
166                 all[i].parentNode.removeChild(all[i]);
167                 console.log("Removed from DOM: " + dependency)
168                 return true
169             } else {
170                 return true;
171             }
172         }
173     }
174     return false;
177 // These functions may be called from scripts that may be out of scope with top so...
178 // if opener is tab then we need to be in tabs UI scope and while we're at it, let's bring webroot along...
180 if (typeof top.tab_mode === "undefined") {
181     if (typeof opener.top.tab_mode !== "undefined") {
182         top.tab_mode = opener.top.tab_mode;
183         top.webroot_url = opener.top.webroot_url;
184     }
186 // We'll need these if out of scope
188 if (typeof top.set_opener !== "function") {
189     var opener_list = [];
191     function set_opener(window, opener) {
192         top.opener_list[window] = opener;
193     }
195     function get_opener(window) {
196         return top.opener_list[window];
197     }
199 // Test if supporting dialog callbacks and close dependencies are in scope.
200 // This is useful when opening and closing the dialog is in the same scope. Still use include_opener.js
201 // in script that will close a dialog that is not in the same scope dlgopen was used
202 // or use parent.dlgclose() if known decendent.
203 // dlgopen() will always have a name whether assigned by dev or created by function.
204 // Callback, onClosed and button clicks are still available either way.
205 // For a callback on close use: dlgclose(functionName, farg1, farg2 ...) which becomes: functionName(farg1,farg2, etc)
207 if (typeof dlgclose !== "function") {
208     if (!opener) {
209         if (!top.tab_mode && typeof top.get_opener === 'function') {
210             opener = top.get_opener(window.name) ? top.get_opener(window.name) : window;
211         } else {
212             opener = window;
213         }
214     }
216     const dlgclose = function (call, args) {
217         var frameName = window.name;
218         var wframe = opener;
219         if (frameName === '') {
220             // try to find dialog. dialogModal is embedded dialog class
221             // It has to be here somewhere.
222             frameName = $(".dialogModal").attr('id');
223             if (!frameName) {
224                 frameName = parent.$(".dialogModal").attr('id');
225                 if (!frameName) {
226                     console.log("Unable to find dialog.");
227                     return false;
228                 }
229             }
230         }
231         if (!top.tab_mode) {
232             for (; wframe.name !== 'RTop' && wframe.name !== 'RBot'; wframe = wframe.parent) {
233                 if (wframe.parent === wframe) {
234                     wframe = window;
235                 }
236             }
237             for (let i = 0; wframe.document.body.localName !== 'body' && i < 4; wframe = wframe[i++]) {
238                 if (i === 3) {
239                     console.log("Opener: unable to find modal's frame");
240                     return false;
241                 }
242             }
243             dialogModal = wframe.$('div#' + frameName);
244             if (dialogModal.length === 0) {
245                 // Never give up...
246                 frameName = $(".dialogModal").attr('id');
247                 dialogModal = wframe.$('div#' + frameName);
248                 console.log("Frame: used local find dialog");
249             }
250         } else {
251             var dialogModal = top.$('div#' + frameName);
252             wframe = top;
253         }
255         var removeFrame = dialogModal.find("iframe[name='" + frameName + "']");
256         if (removeFrame.length > 0) {
257             removeFrame.remove();
258         }
260         if (dialogModal.length > 0) {
261             if (call) {
262                 wframe.setCallBack(call, args); // sets/creates callback function in dialogs scope.
263             }
264             dialogModal.modal('hide');
265         }
266     };
270 * function dlgopen(url, winname, width, height, forceNewWindow, title, opts)
272 * @summary Stackable, resizable and draggable responsive ajax/iframe dialog modal.
274 * @param {url} string Content location.
275 * @param {String} winname If set becomes modal id and/or iframes name. Or, one is created/assigned(iframes).
276 * @param {Number| String} width|modalSize(modal-xlg) For sizing: an number will be converted to a percentage of view port width.
277 * @param {Number} height Initial minimum height. For iframe auto resize starts at this height.
278 * @param {boolean} forceNewWindow Force using a native window.
279 * @param {String} title If exist then header with title is created otherwise no header and content only.
280 * @param {Object} opts Dialogs options.
281 * @returns {Object} dialog object reference.
282 * */
283 function dlgopen(url, winname, width, height, forceNewWindow, title, opts) {
284     // First things first...
285     top.restoreSession();
286     // A matter of Legacy
287     if (forceNewWindow) {
288         return dlgOpenWindow(url, winname, width, height);
289     }
291     // wait for DOM then check dependencies needed to run this feature.
292     // dependency duration is while 'this' is in scope, temporary...
293     // seldom will this get used as more of U.I is moved to Bootstrap
294     // but better to continue than stop because of a dependency...
295     //
296     let jqurl = top.webroot_url + '/public/assets/jquery-1-9-1/jquery.min.js';
297     if (typeof jQuery === 'undefined') {
298         includeScript(jqurl, false, 'script'); // true is async
299     }
300     jQuery(function () {
301         // Check for dependencies we will need.
302         // webroot_url is a global defined in main_screen.php or main.php.
303         let bscss = top.webroot_url + '/public/assets/bootstrap/dist/css/bootstrap.min.css';
304         let bscssRtl = top.webroot_url + '/public/assets/bootstrap-rtl/dist/css/bootstrap-rtl.min.css';
305         let bsurl = top.webroot_url + '/public/assets/bootstrap/dist/js/bootstrap.min.js';
306         let jqui = top.webroot_url + '/public/assets/jquery-ui/jquery-ui.min.js';
308         let version = jQuery.fn.jquery.split(' ')[0].split('.');
309         if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {
310             inDom('jquery-min', 'script', true);
311             includeScript(jqurl, false, 'script');
312             console.log('Replacing jQuery version:[ ' + version + ' ]');
313         }
314         if (!inDom('bootstrap.min.css', 'link', false)) {
315             includeScript(bscss, false, 'link');
316             if (top.jsLanguageDirection === 'rtl') {
317                 includeScript(bscssRtl, false, 'link');
318             }
319         }
320         if (typeof jQuery.fn.modal === 'undefined') {
321             if (!inDom('bootstrap.min.js', 'script', false))
322                 includeScript(bsurl, false, 'script');
323         }
324         if (typeof jQuery.ui === 'undefined') {
325             includeScript(jqui, false, 'script');
326         }
327     });
329     // onward
331     var opts_defaults = {
332         type: 'iframe', // POST, GET (ajax) or iframe
333         async: true,
334         frameContent: "", // for iframe embedded content
335         html: "", // content for alerts, comfirm etc ajax
336         allowDrag: true,
337         allowResize: true,
338         sizeHeight: 'auto', // 'full' will use as much height as allowed
339         // use is onClosed: fnName ... args not supported however, onClosed: 'reload' is auto defined and requires no function to be created.
340         onClosed: false,
341         callBack: false // use {call: 'functionName, args: args, args} if known or use dlgclose.
342     };
343     if (!opts) var opts = {};
345     opts = jQuery.extend({}, opts_defaults, opts);
346     opts.type = opts.type ? opts.type.toLowerCase() : '';
348     var mHeight, mWidth, mSize, msSize, dlgContainer, fullURL, where; // a growing list...
350     if (top.tab_mode) {
351         where = opts.type === 'iframe' ? top : window;
352     } else { // if frames u.i, this will search for the first body node so we have a landing place for stackable's
353         let wframe = window;
354         if (wframe.name !== 'left_nav') {
355             for (let i = 0; wframe.name !== 'RTop' && wframe.name !== 'RBot' && i < 6; wframe = wframe.parent) {
356                 if (i === 5) {
357                     wframe = window;
358                 }
359                 i++;
360             }
361         } else {
362             wframe = top.window['RTop'];
363         }
364         for (let i = 0; wframe.document.body.localName !== 'body' && i < 6; wframe = wframe[i++]) {
365             if (i === 5) {
366                 alert('Unable to find window to build');
367                 return false;
368             }
369         }
371         where = wframe; // A moving target for Frames UI.
372     }
374     // get url straight...
375     var fullURL = "";
376     if (opts.url) {
377         url = opts.url;
378     }
379     if (url) {
380         if (url[0] === "/") {
381             fullURL = url
382         }
383         else {
384             fullURL = window.location.href.substr(0, window.location.href.lastIndexOf("/") + 1) + url;
385         }
386     }
388     // what's a window without a name. important for stacking and opener.
389     winname = (winname === "_blank" || !winname) ? dialogID() : winname;
391     // Convert dialog size to percentages and/or css class.
392     var sizeChoices = ['modal-sm', 'modal-md', 'modal-mlg', 'modal-lg', 'modal-xl'];
393     if (Math.abs(width) > 0) {
394         width = Math.abs(width);
395         mWidth = (width / where.innerWidth * 100).toFixed(4) + '%';
396         msSize = '<style>.modal-custom-' + winname + ' {width:' + mWidth + ';}</style>';
397         mSize = 'modal-custom' + winname;
398     } else if (jQuery.inArray(width, sizeChoices) !== -1) {
399         mSize = width; // is a modal class
400     } else {
401         msSize = '<style>.modal-custom-' + winname + ' {width:35%;}</style>'; // standard B.S. modal default (modal-md)
402     }
403     // leave below for legacy
404     if (mSize === 'modal-sm') {
405         msSize = '<style>.modal-custom-' + winname + ' {width:25%;}</style>';
406     } else if (mSize === 'modal-md') {
407         msSize = '<style>.modal-custom-' + winname + ' {width:40%;}</style>';
408     } else if (mSize === 'modal-mlg') {
409         msSize = '<style>.modal-custom-' + winname + ' {width:55%;}</style>';
410     } else if (mSize === 'modal-lg') {
411         msSize = '<style>.modal-custom-' + winname + ' {width:75%;}</style>';
412     } else if (mSize === 'modal-xl') {
413         msSize = '<style>.modal-custom-' + winname + ' {width:96%;}</style>';
414     }
415     mSize = 'modal-custom-' + winname;
417     // Initial responsive height.
418     var vpht = where.innerHeight;
419     mHeight = height > 0 ? (height / vpht * 100).toFixed(4) + 'vh' : '';
421     // Build modal template. For now !title = !header and modal full height.
422     var mTitle = title > "" ? '<h4 class=modal-title>' + title + '</h4>' : '';
424     var waitHtml =
425         '<div class="loadProgress text-center">' +
426         '<span class="fa fa-circle-o-notch fa-spin fa-3x text-primary"></span>' +
427         '</div>';
429     var headerhtml =
430         ('<div class=modal-header><span type=button class="x close" data-dismiss=modal>' +
431             '<span aria-hidden=true>&times;</span>' +
432             '</span><h5 class=modal-title>%title%</h5></div>')
433             .replace('%title%', mTitle);
435     var frameHead =
436         ('<div><span class="close data-dismiss=modal aria-hidden="true">&times;</span></div>');
438     var frameHtml =
439         ('<iframe id="modalframe" class="embed-responsive-item modalIframe" name="%winname%" %url% frameborder=0></iframe>')
440             .replace('%winname%', winname)
441             .replace('%url%', fullURL ? 'src=' + fullURL : '');
443     var embedded = 'embed-responsive embed-responsive-16by9';
445     var bodyStyles = (' style="margin:2px;padding:2px;height:%initHeight%;max-height:94vh;overflow-y:auto;"')
446         .replace('%initHeight%', opts.sizeHeight !== 'full' ? mHeight : '94vh');
448     var altClose = '<div class="closeDlgIframe" data-dismiss="modal" ></div>';
450     var mhtml =
451         ('<div id="%id%" class="modal fade dialogModal" tabindex="-1" role="dialog">%sStyle%' +
452             '<style>.modal-backdrop{opacity:0; transition:opacity 1s;}.modal-backdrop.in{opacity:0.2;}</style>' +
453             '<div %dialogId% class="modal-dialog %szClass%" role="document">' +
454             '<div class="modal-content">' +
455             '%head%' + '%altclose%' + '%wait%' +
456             '<div class="modal-body %embedded%" %bodyStyles%>' +
457             '%body%' + '</div></div></div></div>')
458             .replace('%id%', winname)
459             .replace('%sStyle%', msSize ? msSize : '')
460             .replace('%dialogId%', opts.dialogId ? ('id=' + opts.dialogId + '"') : '')
461             .replace('%szClass%', mSize ? mSize : '')
462             .replace('%head%', mTitle !== '' ? headerhtml : '')
463             .replace('%altclose%', mTitle === '' ? altClose : '')
464             .replace('%wait%', '') // maybe option later
465             .replace('%bodyStyles%', bodyStyles)
466             .replace('%embedded%', opts.type === 'iframe' ? embedded : '')
467             .replace('%body%', opts.type === 'iframe' ? frameHtml : '');
469     // Write modal template.
470     //
471     dlgContainer = where.jQuery(mhtml);
472     dlgContainer.attr("name", winname);
474     // No url and just iframe content
475     if (opts.frameContent && opts.type === 'iframe') {
476         var ipath = 'data:text/html,' + encodeURIComponent(opts.frameContent);
477         dlgContainer.find("iframe[name='" + winname + "']").attr("src", ipath);
478     }
480     if (opts.buttons) {
481         dlgContainer.find('.modal-content').append(buildFooter());
482     }
483 // Ajax setup
484     if (opts.type === 'alert') {
485         dlgContainer.find('.modal-body').html(opts.html);
486     }
487     if (opts.type !== 'iframe' && opts.type !== 'alert') {
488         var params = {
489             async: opts.async,
490             method: opts.type || '', // if empty and has data object, then post else get.
491             content: opts.data || opts.html, // ajax loads fetched content.
492             url: opts.url || fullURL,
493             dataType: opts.dataType || '' // xml/json/text etc.
494         };
496         dialogAjax(params, dlgContainer);
497     }
499     // let opener array know about us.
500     top.set_opener(winname, window);
502     // Write the completed template to calling document or 'where' window.
503     where.jQuery("body").append(dlgContainer);
505     jQuery(function () {
506         // DOM Ready. Handle events and cleanup.
507         if (opts.type === 'iframe') {
508             var modalwin = where.jQuery('body').find("[name='" + winname + "']");
509             jQuery('div.modal-dialog', modalwin).css({'margin': '15px auto'});
510             modalwin.on('load', function (e) {
511                 setTimeout(function () {
512                     if (opts.sizeHeight === 'auto') {
513                         SizeModaliFrame(e, height);
514                     } else if (opts.sizeHeight === 'fixed') {
515                         sizing(e, height);
516                     } else {
517                         sizing(e, height); // must be full height of container
518                     }
519                 }, 500);
520             });
521         }
523         dlgContainer.on('show.bs.modal', function () {
524             if (opts.allowResize) {
525                 jQuery('.modal-content', this).resizable({
526                     grid: [5, 5],
527                     animate: true,
528                     animateEasing: "swing",
529                     animateDuration: "fast",
530                     alsoResize: jQuery('div.modal-body', this)
531                 })
532             }
533             if (opts.allowDrag) {
534                 jQuery('.modal-dialog', this).draggable({
535                     iframeFix: true,
536                     cursor: false
537                 });
538             }
539         }).on('shown.bs.modal', function () {
540             // Remove waitHtml spinner/loader etc.
541             jQuery(this).parent().find('div.loadProgress')
542                 .fadeOut(function () {
543                     jQuery(this).remove();
544                 });
545             dlgContainer.modal('handleUpdate'); // allow for scroll bar
546         }).on('hidden.bs.modal', function (e) {
547             // remove our dialog
548             jQuery(this).remove();
549             console.log('Modal hidden then removed!');
551             // now we can run functions in our window.
552             if (opts.onClosed) {
553                 console.log('Doing onClosed:[' + opts.onClosed + ']');
554                 if (opts.onClosed === 'reload') {
555                     window.location.reload();
556                 } else {
557                     window[opts.onClosed]();
558                 }
559             }
560             if (opts.callBack.call) {
561                 console.log('Doing callBack:[' + opts.callBack.call + '|' + opts.callBack.args + ']');
562                 if (opts.callBack.call === 'reload') {
563                     window.location.reload();
564                 } else {
565                     window[opts.callBack.call](opts.callBack.args);
566                 }
567             }
569         }).modal({backdrop: 'static', keyboard: true}, 'show');// Show Modal
571         // define local dialog close() function. openers scope
572         window.dlgCloseAjax = function (calling, args) {
573             if (calling) {
574                 opts.callBack = {call: calling, args: args};
575             }
576             dlgContainer.modal('hide'); // important to clean up in only one place, hide event....
577             return false;
578         };
580         // define local callback function. Set with opener or from opener, will exe on hide.
581         window.dlgSetCallBack = function (calling, args) {
582             opts.callBack = {call: calling, args: args};
583             return false;
584         };
586         // in residents dialog scope
587         where.setCallBack = function (calling, args) {
588             opts.callBack = {call: calling, args: args};
589             return true;
590         };
592         where.getOpener = function () {
593             return where;
594         };
596         // Return the dialog ref. looking towards deferring...
597         return dlgContainer;
599     }); // end events
600 // Ajax call with promise
601     function dialogAjax(data, $dialog) {
602         var params = {
603             async: data.async,
604             method: data.method || '',
605             data: data.content,
606             url: data.url,
607             dataType: data.dataType || 'html'
608         };
610         if (data.url) {
611             jQuery.extend(params, data);
612         }
614         jQuery.ajax(params)
615             .done(aOkay)
616             .fail(oops);
618         return true;
620         function aOkay(html) {
621             $dialog.find('.modal-body').html(data.success ? data.success(html) : html);
623             return true;
624         }
626         function oops(r, s) {
627             var msg = data.error ?
628                 data.error(r, s, params) :
629                 '<div class="alert alert-danger">' +
630                 '<strong>XHR Failed:</strong> [ ' + params.url + '].' + '</div>';
632             $dialog.find('.modal-body').html(msg);
634             return false;
635         }
636     }
638     function buildFooter() {
639         if (opts.buttons === false) {
640             return '';
641         }
642         var oFoot = jQuery('<div>').addClass('modal-footer').prop('id', 'oefooter');
643         if (opts.buttons) {
644             for (var i = 0, k = opts.buttons.length; i < k; i++) {
645                 var btnOp = opts.buttons[i];
646                 if (typeof btnOp.class !== 'undefined') {
647                     var btn = jQuery('<button>').addClass('btn ' + (btnOp.class || 'btn-primary'));
648                 } else { // legacy
649                     var btn = jQuery('<button>').addClass('btn btn-' + (btnOp.style || 'primary'));
650                     btnOp.style = "";
651                 }
652                 for (var index in btnOp) {
653                     if (btnOp.hasOwnProperty(index)) {
654                         switch (index) {
655                             case 'close':
656                                 //add close event
657                                 if (btnOp[index]) {
658                                     btn.attr('data-dismiss', 'modal');
659                                 }
660                                 break;
661                             case 'click':
662                                 //binds button to click event of fn defined in calling document/form
663                                 var fn = btnOp.click.bind(dlgContainer.find('.modal-content'));
664                                 btn.click(fn);
665                                 break;
666                             case 'text':
667                                 btn.html(btnOp[index]);
668                                 break;
669                             case 'class':
670                                 break;
671                             default:
672                                 //all other possible HTML attributes to button element
673                                 // name, id etc
674                                 btn.attr(index, btnOp[index]);
675                         }
676                     }
677                 }
679                 oFoot.append(btn);
680             }
681         } else {
682             //if no buttons defined by user, add a standard close button.
683             oFoot.append('<button class="closeBtn btn btn-default" data-dismiss=modal type=button><i class="fa fa-times-circle"></i></button>');
684         }
686         return oFoot; // jquery object of modal footer.
687     }
689     // dynamic sizing - special case for full height - @todo use for fixed wt and ht
690     function sizing(e, height) {
691         let viewPortHt = 0;
692         let $idoc = jQuery(e.currentTarget);
693         if (top.tab_mode) {
694             viewPortHt = Math.max(top.window.document.documentElement.clientHeight, top.window.innerHeight || 0);
695             viewPortWt = Math.max(top.window.document.documentElement.clientWidth, top.window.innerWidth || 0);
696         } else {
697             viewPortHt = window.innerHeight || 0;
698             viewPortWt = window.innerWidth || 0;
699         }
700         let frameContentHt = opts.sizeHeight === 'full' ? viewPortHt : height;
701         frameContentHt = frameContentHt > viewPortHt ? viewPortHt : frameContentHt;
702         let hasHeader = $idoc.parents('div.modal-content').find('div.modal-header').height() || 0;
703         let hasFooter = $idoc.parents('div.modal-content').find('div.modal-footer').height() || 0;
704         frameContentHt = frameContentHt - hasHeader - hasFooter;
705         size = (frameContentHt / viewPortHt * 100).toFixed(4);
706         let maxsize = hasHeader ? 90 : hasFooter ? 86.5 : 95.5;
707         maxsize = hasHeader && hasFooter ? 80 : maxsize;
708         maxsize = maxsize + 'vh';
709         size = size + 'vh';
710         $idoc.parents('div.modal-body').css({'height': size, 'max-height': maxsize, 'max-width': '96vw'});
711         console.log('Modal loaded and sized! Content:' + frameContentHt + ' Viewport:' + viewPortHt + ' Modal height:' +
712             size + ' Type:' + opts.sizeHeight + ' Width:' + hasHeader + ' isFooter:' + hasFooter);
714         return size;
715     }
717     // sizing for modals with iframes
718     function SizeModaliFrame(e, minSize) {
719         let viewPortHt;
720         let idoc = e.currentTarget.contentDocument ? e.currentTarget.contentDocument : e.currentTarget.contentWindow.document;
721         jQuery(e.currentTarget).parents('div.modal-content').height('');
722         jQuery(e.currentTarget).parent('div.modal-body').css({'height': 0});
723         if (top.tab_mode) {
724             viewPortHt = top.window.innerHeight || 0;
725         } else {
726             viewPortHt = where.window.innerHeight || 0;
727         }
728         //minSize = 100;
729         let frameContentHt = Math.max(jQuery(idoc).height(), idoc.body.offsetHeight || 0) + 30;
730         frameContentHt = frameContentHt < minSize ? minSize : frameContentHt;
731         frameContentHt = frameContentHt > viewPortHt ? viewPortHt : frameContentHt;
732         let hasHeader = jQuery(e.currentTarget).parents('div.modal-content').find('div.modal-header').length;
733         let hasFooter = jQuery(e.currentTarget).parents('div.modal-content').find('div.modal-footer').length;
734         size = (frameContentHt / viewPortHt * 100).toFixed(4);
735         let maxsize = hasHeader ? 90 : hasFooter ? 87.5 : 96;
736         maxsize = hasHeader && hasFooter ? 80 : maxsize;
737         maxsize = maxsize + 'vh';
738         size = size + 'vh'; // will start the dialog as responsive. Any resize by user turns dialog to absolute positioning.
740         jQuery(e.currentTarget).parent('div.modal-body').css({'height': size, 'max-height': maxsize}); // Set final size. Width was previously set.
741         //jQuery(e.currentTarget).parent('div.modal-body').height(size)
742         console.log('Modal loaded and sized! Content:' + frameContentHt + ' Viewport:' + viewPortHt + ' Modal height:' +
743             size + ' Max height:' + maxsize + ' isHeader:' + (hasHeader > 0 ? 'True ' : 'False ') + ' isFooter:' + (hasFooter > 0 ? 'True' : 'False'));
745         return size;
746     }