some cleanup - several user interface improvements
[openemr.git] / library / dialog.js
blob8c2b41b94bfd31b4f3ba8e9ec72d19588a6fcc07
1 // Copyright (C) 2005 Rod Roark <rod@sunsetsystems.com>
2 //
3 // This program is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU General Public License
5 // as published by the Free Software Foundation; either version 2
6 // of the License, or (at your option) any later version.
8 // open a new cascaded window
9 function cascwin(url, winname, width, height, options) {
10     var mywin = window.parent ? window.parent : window;
11     var newx = 25, newy = 25;
12     if (!isNaN(mywin.screenX)) {
13         newx += mywin.screenX;
14         newy += mywin.screenY;
15     } else if (!isNaN(mywin.screenLeft)) {
16         newx += mywin.screenLeft;
17         newy += mywin.screenTop;
18     }
19     if ((newx + width) > screen.width || (newy + height) > screen.height) {
20         newx = 0;
21         newy = 0;
22     }
23     top.restoreSession();
25     // MS IE version detection taken from
26     // http://msdn2.microsoft.com/en-us/library/ms537509.aspx
27     // to adjust the height of this box for IE only -- JRM
28     if (navigator.appName == 'Microsoft Internet Explorer') {
29         var ua = navigator.userAgent;
30         var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
31         if (re.exec(ua) != null)
32             rv = parseFloat(RegExp.$1); // this holds the version number
33         height = height + 28;
34     }
36     retval = window.open(url, winname, options +
37         ",width=" + width + ",height=" + height +
38         ",left=" + newx + ",top=" + newy +
39         ",screenX=" + newx + ",screenY=" + newy);
41     return retval;
44 // recursive window focus-event grabber
45 function grabfocus(w) {
46     for (var i = 0; i < w.frames.length; ++i) grabfocus(w.frames[i]);
47     w.onfocus = top.imfocused;
50 // Call this when a "modal" dialog is desired.
51 // Note that the below function is used for the
52 // frames ui, and that a separate dlgopen function
53 // is used below (see if(top.tab_mode)...) for the tabs ui.
54 function dlgopen(url, winname, width, height) {
55     if (top.modaldialog && !top.modaldialog.closed) {
56         if (window.focus) top.modaldialog.focus();
57         if (top.modaldialog.confirm(top.oemr_dialog_close_msg)) {
58             top.modaldialog.close();
59             top.modaldialog = null;
60         } else {
61             return false;
62         }
63     }
64     top.modaldialog = cascwin(url, winname, width, height,
65         "resizable=1,scrollbars=1,location=0,toolbar=0");
66     grabfocus(top);
67     return false;
70 // This is called from del_related() which in turn is invoked by find_code_dynamic.php.
71 // Deletes the specified codetype:code from the indicated input text element.
72 function my_del_related(s, elem, usetitle) {
73     if (!s) {
74         // Deleting everything.
75         elem.value = '';
76         if (usetitle) {
77             elem.title = '';
78         }
79         return;
80     }
81     // Convert the codes and their descriptions to arrays for easy manipulation.
82     var acodes = elem.value.split(';');
83     var i = acodes.indexOf(s);
84     if (i < 0) {
85         return; // not found, should not happen
86     }
87     // Delete the indicated code and description and convert back to strings.
88     acodes.splice(i, 1);
89     elem.value = acodes.join(';');
90     if (usetitle) {
91         var atitles = elem.title.split(';');
92         atitles.splice(i, 1);
93         elem.title = atitles.join(';');
94     }
97 function dialogID() {
98     function s4() {
99         return Math.floor((1 + Math.random()) * 0x10000)
100             .toString(16)
101             .substring(1);
102     }
104     return s4() + s4() + s4() + s4() + s4() + +s4() + s4() + s4();
108 * @summary Responsive dialog modal for tabs interface.
109 * Currently for iframe w/wo header. Still uses opener and window close and resides in top frame.
111 * @param string url iframe Content location.
112 * @param string winname Already opened frame name. (depreciate)
113 * @param number width|modalType For sizing: an int will be converted to a percentage of view port width.
114 * @param number height Ignored for now.
115 * @param boolean forceNewWindow Force using a native window.
116 * @param string title If exist then header with title is created otherwise no header and content only.
117 * @returns none.
118 * */
119 if (top.tab_mode) {
120     dlgOpenWindow = dlgopen;
121     dlgopen = function (url, winname, width, height, forceNewWindow, title) {
122         top.restoreSession();
124         if (forceNewWindow) {
125             return dlgOpenWindow(url, winname, width, height);
126         }
128         var fullURL;
129         if (url[0] === "/") {
130             fullURL = url
131         }
132         else {
133             fullURL = window.location.href.substr(0, window.location.href.lastIndexOf("/") + 1) + url;
134         }
136         /* Depreciate 11/5/17 sjp.
137         var dialogDiv = top.$("#dialogDiv");
138             var dlgIframe = {};
139             if (winname !== "_blank") {
140             dlgIframe = dialogDiv.find("iframe[name='" + winname + "']");
141             }
142             else {
143             winname = dialogID();
144         }
145         */
147         winname = dialogID();
148         var mHeight, mWidth, mSize, msSize, dlgDivContainer;
150         // Convert legacy dialog size to percentages and/or css classes.
151         var sizeChoices = ['modal-sm', 'modal-md', 'modal-mlg', 'modal-lg', 'modal-xl'];
152         if (Number.isInteger(width)) {
153             width = Math.abs(width);
154             mWidth = (width / top.window.innerWidth * 100).toFixed(3) + '%';
155             msSize = '<style>.modal-custom' + winname + ' {width:' + mWidth + ';}</style>';
156             mSize = 'modal-custom' + winname;
157         } else if ($.inArray(width, sizeChoices)) {
158             mSize = width; // is a modal class
159         } else {
160             msSize = 'default'; // standard B.S. modal default (modal-md)
161         }
163         if (mSize === 'modal-sm') {
164             msSize = '<style>.modal-sm {width:25%;}</style>';
165         } else if (mSize === 'modal-md') {
166             msSize = '<style>.modal-md {width:50%;}</style>';
167         } else if (mSize === 'modal-mlg') {
168             msSize = '<style>.modal-mlg {width:60%;}</style>';
169         } else if (mSize === 'modal-lg') {
170             msSize = '<style>.modal-lg {width:75%;}</style>';
171         } else if (mSize === 'modal-xlg') {
172             msSize = '<style>.modal-xl {width:96%;}</style>';
173         }
175         // Guess at initial responsive height. @TODO Make option for fixed modal size.
176         mHeight = (height / top.window.innerHeight * 100).toFixed(3) + 'vh';
178         // Build modal html
179         var mTitle = title > "" ? '<h4>' + title + '</h4>' : ''; // For now !title = !header and modal full height. Emulates legacy.
181         var waitHtml =
182             '<div class="loadProgress text-center">' +
183             '<span class="fa fa-circle-o-notch fa-spin fa-3x text-primary"></span>' +
184             '</div>';
186         var headerhtml =
187             ('<div class="modal-header">%title%</div>')
188                 .replace('%title%', mTitle);
190         var mhtml =
191             ('<div id="%id%" class="modal fade dialogModal" tabindex="-1" role="dialog">%sStyle%' +
192                 '<div class="modal-dialog %szClass%" role="document">' +
193                 '<div class="modal-content">' +
194                 '%head%' +
195                 '<div class="closeDlgIframe" data-dismiss="modal" ></div>' +
196                 '<div class="modal-body" style="height:%initHeight%;">' +
197                 '<iframe id="mIframe" class="modalIframe" name="%winname%" frameborder=0 ' +
198                 'style="width:100%;height:100%;overflow-y:auto;display:block;" src="%url%">' +
199                 '</iframe></div></div></div></div>')
200                 .replace('%winname%', winname)
201                 .replace('%id%', winname)
202                 .replace('%head%', mTitle !== "" ? headerhtml : "")
203                 .replace('%url%', fullURL)
204                 .replace('%szClass%', mSize)
205                 .replace('%initHeight%', mHeight) // May have use later for options.
206                 .replace('%sStyle%', msSize !== "default" ? msSize : ''); // default is bootstrap's default.
208         // Write modal html to top window where opener can manage.
209         dlgDivContainer = top.$(mhtml);
210         dlgDivContainer.attr("name", winname);
211         top.$("body").append(dlgDivContainer);
213         // let opener array know about us.
214         top.set_opener(winname, window);
216         // Setup events needed to Calc size, detect window.close, cleanup and auto size modal height on show.
217         $(function () { // DOM Ready.
218             var modalwin = top.$('body').find("[name='" + winname + "']");
219             modalwin.on('load', function (e) {
220                 // Larger dialog content may not be completely parsed/rendered even though load event is fired.
221                 // Need a little extra time. Adjust here if you encounter auto height default on long frame content.
222                 // Not sure why this is but, needed to get accurate content height for auto sizing!!
223                 //
224                 setTimeout(function () {
225                     SizeModal(e); // auto size
226                 }, 250);
228             });
230             top.$('#' + winname).on('show.bs.modal', function () {
232                 $('div.modal-dialog', this).css({'margin': '15px auto'});
233                 // leave margins and padding to iframe content style.
234                 $('div.modal-body', this).css({
235                     'width': 'auto',
236                     'margin': 'auto',
237                     'padding': 'auto',
238                     'max-height': '100%'
239                 });
241             });
243             top.$('#' + winname).on('shown.bs.modal', function () {
244                 // Remove waitHtml spinner/loader etc.
245                 $(this).parent().find('div.loadProgress')
246                     .fadeOut(function () {
247                         $(this).remove();
248                     });
250                 // Using jquery-ui plug-in that is loaded in main.php header class.
251                 // Cursor styles are in tab sheets.
252                 //
253                 top.$('.modal-content', this).resizable({
254                     alsoResize: top.$('div.modal-body', this)
255                 });
256                 top.$('.modal-dialog', this).draggable();
258                 top.$('#' + winname).modal('handleUpdate'); // allow for scroll bar
260             });
262             // Remove modal html on close. Event is fired from modal close x or window.close()
263             // via include_opener.js where the iframe is destroyed.
264             //
265             top.$('#' + winname).on('hidden.bs.modal', function () {
266                 console.log('Modal hidden then removed!');
267                 $(this).remove();
269             });
271             // Show Modal
272             top.$('#' + winname).modal({backdrop: 'static', keyboard: true}, 'show');
274         });
277     };
281 // Do sizing based on view port and frame content length.
282 function SizeModal(e) {
284     var idoc = e.currentTarget.contentDocument ? e.currentTarget.contentDocument : e.currentTarget.contentWindow.document;
285     var frameContentHt = idoc.body.offsetHeight + 60; // add for pad.
286     var viewPortHt = top.window.innerHeight;
287     var size = (frameContentHt / viewPortHt * 100).toFixed(3); // scale to content plus 5% padding.
288     var header = $(e.currentTarget).parent('div.modal-header')[0] ? true : false; // if header missing we need to know.
290     if (size < 25) {
291         size = 25; // set a min height percentage.
292     } else if (size > 87.5) {
293         if (!header) { // no header so use all view port height that we dare.
294             size = 92.5;
295         }
296         else {
297             size = 87.5; // set max height and allow for a header.
298         }
299     }
301     size = size + 'vh'; // will start the dialog as responsive. Any resize by user turns dialog to absolute positioning.
302     $(e.currentTarget).parent('div.modal-body').css({'padding-right': '10px', 'height': size}); // Set final size. Width was previously set.
304     console.log('Modal loaded and sized! Content:' + frameContentHt + ' Viewport:' + viewPortHt + ' Modal height:' + size);
306     return;