1 // Copyright (C) 2005 Rod Roark <rod@sunsetsystems.com>
2 // Copyright (C) 2018 Jerry Padgett <sjpadgett@gmail.com>
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;
20 if ((newx + width) > screen.width || (newy + height) > screen.height) {
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
37 retval = window.open(url, winname, options +
38 ",width=" + width + ",height=" + height +
39 ",left=" + newx + ",top=" + newy +
40 ",screenX=" + newx + ",screenY=" + newy);
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;
66 top.modaldialog = cascwin(url, winname, width, height,
67 "resizable=1,scrollbars=1,location=0,toolbar=0");
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) {
76 // Deleting everything.
83 // Convert the codes and their descriptions to arrays for easy manipulation.
84 var acodes = elem.value.split(';');
85 var i = acodes.indexOf(s);
87 return; // not found, should not happen
89 // Delete the indicated code and description and convert back to strings.
91 elem.value = acodes.join(';');
93 var atitles = elem.title.split(';');
95 elem.title = atitles.join(';');
101 return Math.floor((1 + Math.random()) * 0x10000)
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'.
119 function includeScript(url, async, type) {
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 + ' ]');
134 rqit.open("GET", url, async); // false = synchronous.
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.
149 throw new Error('<?php echo xlt("Failed to get URL:") ?>' + url);
158 // test for and/or remove dependency.
159 function inDom(dependency, type, remove) {
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) {
166 all[i].parentNode.removeChild(all[i]);
167 console.log("Removed from DOM: " + dependency)
177 // Test if supporting dialog callbacks and close dependencies are in scope.
178 // This is useful when opening and closing the dialog is in the same scope. Still use include_opener.js
179 // in script that will close a dialog that is not in the same scope dlgopen was used
180 // or use parent.dlgclose() if known decendent.
181 // dlgopen() will always have a name whether assigned by dev or created by function.
182 // Callback, onClosed and button clicks are still available either way.
183 // For a callback on close use: dlgclose(functionName, farg1, farg2 ...) which becomes: functionName(farg1,farg2, etc)
185 if (typeof dlgclose !== "function") {
187 if (!top.tab_mode && typeof top.get_opener === 'function') {
188 opener = top.get_opener(window.name) ? top.get_opener(window.name) : window;
195 function (call, args) {
196 var frameName = window.name;
198 if (frameName === '') {
199 // try to find dialog. dialogModal is embedded dialog class
200 // It has to be here somewhere.
201 frameName = $(".dialogModal").attr('id');
203 frameName = parent.$(".dialogModal").attr('id');
205 console.log("Unable to find dialog.");
211 for (; wframe.name !== 'RTop' && wframe.name !== 'RBot'; wframe = wframe.parent) {
212 if (wframe.parent === wframe) {
216 for (let i = 0; wframe.document.body.localName !== 'body' && i < 4; wframe = wframe[i++]) {
218 console.log("Opener: unable to find modal's frame");
222 dialogModal = wframe.$('div#' + frameName);
223 if (dialogModal.length === 0) {
225 frameName = $(".dialogModal").attr('id');
226 dialogModal = wframe.$('div#' + frameName);
227 console.log("Frame: used local find dialog");
230 var dialogModal = top.$('div#' + frameName);
234 var removeFrame = dialogModal.find("iframe[name='" + frameName + "']");
235 if (removeFrame.length > 0) {
236 removeFrame.remove();
239 if (dialogModal.length > 0) {
241 wframe.setCallBack(call, args); // sets/creates callback function in dialogs scope.
243 dialogModal.modal('hide');
249 * function dlgopen(url, winname, width, height, forceNewWindow, title, opts)
251 * @summary Stackable, resizable and draggable responsive ajax/iframe dialog modal.
253 * @param {url} string Content location.
254 * @param {String} winname If set becomes modal id and/or iframes name. Or, one is created/assigned(iframes).
255 * @param {Number| String} width|modalSize(modal-xlg) For sizing: an number will be converted to a percentage of view port width.
256 * @param {Number} height Initial minimum height. For iframe auto resize starts at this height.
257 * @param {boolean} forceNewWindow Force using a native window.
258 * @param {String} title If exist then header with title is created otherwise no header and content only.
259 * @param {Object} opts Dialogs options.
260 * @returns {Object} dialog object reference.
262 function dlgopen(url, winname, width, height, forceNewWindow, title, opts) {
263 // First things first...
264 top.restoreSession();
265 // A matter of Legacy
266 if (forceNewWindow) {
267 return dlgOpenWindow(url, winname, width, height);
270 // wait for DOM then check dependencies needed to run this feature.
271 // dependency duration is while 'this' is in scope, temporary...
272 // seldom will this get used as more of U.I is moved to Bootstrap
273 // but better to continue than stop because of a dependency...
275 let jqurl = top.webroot_url + '/public/assets/jquery-1-9-1/jquery.min.js';
276 if (typeof jQuery === 'undefined') {
277 includeScript(jqurl, false, 'script'); // true is async
280 // Check for dependencies we will need.
281 // webroot_url is a global defined in main_screen.php or main.php.
283 let bscss = top.webroot_url + '/public/assets/bootstrap/dist/css/bootstrap.min.css';
284 let bscssRtl = top.webroot_url + '/public/assets/bootstrap-rtl/dist/css/bootstrap-rtl.min.css';
285 let bsurl = top.webroot_url + '/public/assets/bootstrap/dist/js/bootstrap.min.js';
286 let jqui = top.webroot_url + '/public/assets/jquery-ui/jquery-ui.min.js';
288 let version = jQuery.fn.jquery.split(' ')[0].split('.');
289 if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {
290 inDom('jquery-min', 'script', true);
291 includeScript(jqurl, false, 'script');
292 console.log('Replacing jQuery version:[ ' + version + ' ]');
294 if (!inDom('bootstrap.min.css', 'link', false)) {
295 includeScript(bscss, false, 'link');
296 if (top.jsLanguageDirection === 'rtl') {
297 includeScript(bscssRtl, false, 'link');
300 if (typeof jQuery.fn.modal === 'undefined') {
301 if (!inDom('bootstrap.min.js', 'script', false))
302 includeScript(bsurl, false, 'script');
304 if (typeof jQuery.ui === 'undefined') {
305 includeScript(jqui, false, 'script');
310 var opts_defaults = {
311 type: 'iframe', // POST, GET (ajax) or iframe
312 frameContent: "", // for iframe embedded content
313 ajaxhtml: "", // content for alerts, comfirm etc ajax
316 sizeHeight: 'auto', // 'full' will use as much height as allowed
317 // use is onClosed: fnName ... args not supported however, onClosed: 'reload' is auto defined and requires no function to be created.
319 callBack: false // use {call: 'functionName, args: args, args} if known or use dlgclose.
322 if (!opts) var opts = {};
324 opts = jQuery.extend({}, opts_defaults, opts);
326 var mHeight, mWidth, mSize, msSize, dlgContainer, fullURL, where; // a growing list...
329 where = opts.type === 'iframe' ? top : window;
330 } else { // if frames u.i, this will search for the first body node so we have a landing place for stackable's
332 if (wframe.name !== 'left_nav') {
333 for (let i = 0; wframe.name !== 'RTop' && wframe.name !== 'RBot' && i < 6; wframe = wframe.parent) {
340 wframe = top.window['RTop'];
342 for (let i = 0; wframe.document.body.localName !== 'body' && i < 6; wframe = wframe[i++]) {
344 alert('<?php echo xlt("Unable to find window to build") ?>');
349 where = wframe; // A moving target for Frames UI.
352 // get url straight...
358 if (url[0] === "/") {
362 fullURL = window.location.href.substr(0, window.location.href.lastIndexOf("/") + 1) + url;
366 // what's a window without a name. important for stacking and opener.
367 winname = (winname === "_blank" || !winname) ? dialogID() : winname;
369 // Convert dialog size to percentages and/or css class.
370 var sizeChoices = ['modal-sm', 'modal-md', 'modal-mlg', 'modal-lg', 'modal-xl'];
371 if (Math.abs(width) > 0) {
372 width = Math.abs(width);
373 mWidth = (width / where.innerWidth * 100).toFixed(4) + '%';
374 msSize = '<style>.modal-custom-' + winname + ' {width:' + mWidth + ';}</style>';
375 mSize = 'modal-custom' + winname;
376 } else if (jQuery.inArray(width, sizeChoices) !== -1) {
377 mSize = width; // is a modal class
379 msSize = '<style>.modal-custom-' + winname + ' {width:35%;}</style>'; // standard B.S. modal default (modal-md)
381 // leave below for legacy
382 if (mSize === 'modal-sm') {
383 msSize = '<style>.modal-custom-' + winname + ' {width:25%;}</style>';
384 } else if (mSize === 'modal-md') {
385 msSize = '<style>.modal-custom-' + winname + ' {width:40%;}</style>';
386 } else if (mSize === 'modal-mlg') {
387 msSize = '<style>.modal-custom-' + winname + ' {width:55%;}</style>';
388 } else if (mSize === 'modal-lg') {
389 msSize = '<style>.modal-custom-' + winname + ' {width:75%;}</style>';
390 } else if (mSize === 'modal-xl') {
391 msSize = '<style>.modal-custom-' + winname + ' {width:96%;}</style>';
393 mSize = 'modal-custom-' + winname;
395 // Initial responsive height.
396 var vpht = where.innerHeight;
397 mHeight = height > 0 ? (height / vpht * 100).toFixed(4) + 'vh' : '';
399 // Build modal template. For now !title = !header and modal full height.
400 var mTitle = title > "" ? '<h4 class=modal-title>' + title + '</h4>' : '';
403 '<div class="loadProgress text-center">' +
404 '<span class="fa fa-circle-o-notch fa-spin fa-3x text-primary"></span>' +
408 ('<div class=modal-header><span type=button class="x close" data-dismiss=modal>' +
409 '<span aria-hidden=true>×</span>' +
410 '</span><h5 class=modal-title>%title%</h5></div>')
411 .replace('%title%', mTitle);
414 ('<div><span class="close data-dismiss=modal aria-hidden="true">×</span></div>');
417 ('<iframe id="modalframe" class="embed-responsive-item modalIframe" name="%winname%" %url% frameborder=0></iframe>')
418 .replace('%winname%', winname)
419 .replace('%url%', fullURL ? 'src=' + fullURL : '');
421 var embedded = 'embed-responsive embed-responsive-16by9';
423 var bodyStyles = (' style="margin:2px;padding:2px;height:%initHeight%;max-height:94vh;overflow-y:auto;"')
424 .replace('%initHeight%', opts.sizeHeight !== 'full' ? mHeight : '94vh');
426 var altClose = '<div class="closeDlgIframe" data-dismiss="modal" ></div>';
429 ('<div id="%id%" class="modal fade dialogModal" tabindex="-1" role="dialog">%sStyle%' +
430 '<style>.modal-backdrop{opacity:0; transition:opacity 1s;}.modal-backdrop.in{opacity:0.2;}</style>' +
431 '<div %dialogId% class="modal-dialog %szClass%" role="document">' +
432 '<div class="modal-content">' +
433 '%head%' + '%altclose%' + '%wait%' +
434 '<div class="modal-body %embedded%" %bodyStyles%>' +
435 '%body%' + '</div></div></div></div>')
436 .replace('%id%', winname)
437 .replace('%sStyle%', msSize ? msSize : '')
438 .replace('%dialogId%', opts.dialogId ? ('id=' + opts.dialogId + '"') : '')
439 .replace('%szClass%', mSize ? mSize : '')
440 .replace('%head%', mTitle !== '' ? headerhtml : '')
441 .replace('%altclose%', mTitle === '' ? altClose : '')
442 .replace('%wait%', '') // maybe option later
443 .replace('%bodyStyles%', bodyStyles)
444 .replace('%embedded%', opts.type === 'iframe' ? embedded : '')
445 .replace('%body%', opts.type === 'iframe' ? frameHtml : '');
447 // Write modal template.
449 dlgContainer = where.jQuery(mhtml);
450 dlgContainer.attr("name", winname);
452 // No url and just iframe content
453 if (opts.frameContent && opts.type === 'iframe') {
454 var ipath = 'data:text/html,' + encodeURIComponent(opts.frameContent);
455 dlgContainer.find("iframe[name='" + winname + "']").attr("src", ipath);
459 dlgContainer.find('.modal-content').append(buildFooter());
462 if (opts.type !== 'iframe') {
464 method: opts.type || '', // if empty and has data object, then post else get.
465 content: opts.data || opts.html || '', // ajax loads fetched content or supplied html. think alerts.
466 url: opts.url || fullURL,
467 dataType: opts.dataType || '' // xml/json/text etc.
470 dialogAjax(params, dlgContainer);
473 // let opener array know about us.
474 top.set_opener(winname, window);
476 // Write the completed template to calling document or 'where' window.
477 where.jQuery("body").append(dlgContainer);
480 // DOM Ready. Handle events and cleanup.
481 if (opts.type === 'iframe') {
482 var modalwin = where.jQuery('body').find("[name='" + winname + "']");
483 jQuery('div.modal-dialog', modalwin).css({'margin': '15px auto'});
484 modalwin.on('load', function (e) {
485 setTimeout(function () {
486 if (opts.sizeHeight === 'auto') {
487 SizeModaliFrame(e, height);
488 } else if (opts.sizeHeight === 'fixed') {
491 sizing(e, height); // must be full height of container
497 dlgContainer.on('show.bs.modal', function () {
498 if (opts.allowResize) {
499 jQuery('.modal-content', this).resizable({
502 animateEasing: "swing",
503 animateDuration: "fast",
504 alsoResize: jQuery('div.modal-body', this)
507 if (opts.allowDrag) {
508 jQuery('.modal-dialog', this).draggable({
513 }).on('shown.bs.modal', function () {
514 // Remove waitHtml spinner/loader etc.
515 jQuery(this).parent().find('div.loadProgress')
516 .fadeOut(function () {
517 jQuery(this).remove();
519 dlgContainer.modal('handleUpdate'); // allow for scroll bar
520 }).on('hidden.bs.modal', function (e) {
522 jQuery(this).remove();
523 console.log('Modal hidden then removed!');
525 // now we can run functions in our window.
527 console.log('Doing onClosed:[' + opts.onClosed + ']');
528 if (opts.onClosed === 'reload') {
529 window.location.reload();
531 window[opts.onClosed]();
534 if (opts.callBack.call) {
535 console.log('Doing callBack:[' + opts.callBack.call + '|' + opts.callBack.args + ']');
536 if (opts.callBack.call === 'reload') {
537 window.location.reload();
539 window[opts.callBack.call](opts.callBack.args);
543 }).modal({backdrop: 'static', keyboard: true}, 'show');// Show Modal
545 // define local dialog close() function. openers scope
546 window.dlgCloseAjax = function (calling, args) {
548 opts.callBack = {call: calling, args: args};
550 dlgContainer.modal('hide'); // important to clean up in only one place, hide event....
554 // define local callback function. Set with opener or from opener, will exe on hide.
555 window.dlgSetCallBack = function (calling, args) {
556 opts.callBack = {call: calling, args: args};
560 // in residents dialog scope
561 where.setCallBack = function (calling, args) {
562 opts.callBack = {call: calling, args: args};
566 where.getOpener = function () {
570 // Return the dialog ref. looking towards deferring...
574 // Ajax call with promise
575 function dialogAjax(data, $dialog) {
578 method: data.method || '',
580 url: data.url || data,
581 dataType: data.dataType || 'text'
585 jQuery.extend(params, data);
594 function aOkay(html) {
595 $dialog.find('.modal-body').html(data.success ? data.success(html) : html);
600 function oops(r, s) {
601 var msg = data.error ?
602 data.error(r, s, params) :
603 '<div class="alert alert-danger">' +
604 '<strong><?php echo xlt("XHR Failed:") ?></strong> [ ' + params.url + '].' + '</div>';
606 $dialog.find('.modal-body').html(msg);
612 function buildFooter() {
613 if (opts.buttons === false) {
616 var oFoot = jQuery('<div>').addClass('modal-footer').prop('id', 'oefooter');
618 for (var i = 0, k = opts.buttons.length; i < k; i++) {
619 var btnOp = opts.buttons[i];
620 var btn = jQuery('<button>').addClass('btn btn-' + (btnOp.style || 'primary'));
622 for (var index in btnOp) {
623 if (btnOp.hasOwnProperty(index)) {
628 btn.attr('data-dismiss', 'modal');
632 //binds button to click event of fn defined in calling document/form
633 var fn = btnOp.click.bind(dlgContainer.find('.modal-content'));
637 btn.html(btnOp[index]);
640 //all other possible HTML attributes to button element
641 btn.attr(index, btnOp[index]);
649 //if no buttons defined by user, add a standard close button.
650 oFoot.append('<button class="closeBtn btn btn-default" data-dismiss=modal type=button><?php echo xlt("Close") ?></button>');
653 return oFoot; // jquery object of modal footer.
656 // dynamic sizing - special case for full height - @todo use for fixed wt and ht
657 function sizing(e, height) {
659 let $idoc = jQuery(e.currentTarget);
661 viewPortHt = Math.max(top.window.document.documentElement.clientHeight, top.window.innerHeight || 0);
662 viewPortWt = Math.max(top.window.document.documentElement.clientWidth, top.window.innerWidth || 0);
664 viewPortHt = window.innerHeight || 0;
665 viewPortWt = window.innerWidth || 0;
667 let frameContentHt = opts.sizeHeight === 'full' ? viewPortHt : height;
668 frameContentHt = frameContentHt > viewPortHt ? viewPortHt : frameContentHt;
669 let hasHeader = $idoc.parents('div.modal-content').find('div.modal-header').height() || 0;
670 let hasFooter = $idoc.parents('div.modal-content').find('div.modal-footer').height() || 0;
671 frameContentHt = frameContentHt - hasHeader - hasFooter;
672 size = (frameContentHt / viewPortHt * 100).toFixed(4);
673 let maxsize = hasHeader ? 90 : hasFooter ? 86.5 : 95.5;
674 maxsize = hasHeader && hasFooter ? 80 : maxsize;
675 maxsize = maxsize + 'vh';
677 $idoc.parents('div.modal-body').css({'height': size, 'max-height': maxsize, 'max-width': '96vw'});
678 console.log('Modal loaded and sized! Content:' + frameContentHt + ' Viewport:' + viewPortHt + ' Modal height:' +
679 size + ' Type:' + opts.sizeHeight + ' Width:' + hasHeader + ' isFooter:' + hasFooter);
684 // sizing for modals with iframes
685 function SizeModaliFrame(e, minSize) {
687 let idoc = e.currentTarget.contentDocument ? e.currentTarget.contentDocument : e.currentTarget.contentWindow.document;
688 jQuery(e.currentTarget).parents('div.modal-content').height('');
689 jQuery(e.currentTarget).parent('div.modal-body').css({'height': 0});
691 viewPortHt = top.window.innerHeight || 0;
693 viewPortHt = where.window.innerHeight || 0;
696 let frameContentHt = Math.max(jQuery(idoc).height(), idoc.body.offsetHeight || 0) + 30;
697 frameContentHt = frameContentHt < minSize ? minSize : frameContentHt;
698 frameContentHt = frameContentHt > viewPortHt ? viewPortHt : frameContentHt;
699 let hasHeader = jQuery(e.currentTarget).parents('div.modal-content').find('div.modal-header').length;
700 let hasFooter = jQuery(e.currentTarget).parents('div.modal-content').find('div.modal-footer').length;
701 size = (frameContentHt / viewPortHt * 100).toFixed(4);
702 let maxsize = hasHeader ? 90 : hasFooter ? 87.5 : 96;
703 maxsize = hasHeader && hasFooter ? 80 : maxsize;
704 maxsize = maxsize + 'vh';
705 size = size + 'vh'; // will start the dialog as responsive. Any resize by user turns dialog to absolute positioning.
707 jQuery(e.currentTarget).parent('div.modal-body').css({'height': size, 'max-height': maxsize}); // Set final size. Width was previously set.
708 //jQuery(e.currentTarget).parent('div.modal-body').height(size)
709 console.log('Modal loaded and sized! Content:' + frameContentHt + ' Viewport:' + viewPortHt + ' Modal height:' +
710 size + ' Max height:' + maxsize + ' isHeader:' + (hasHeader > 0 ? 'True ' : 'False ') + ' isFooter:' + (hasFooter > 0 ? 'True' : 'False'));