// Copyright (C) 2005 Rod Roark // Copyright (C) 2018-2021 Jerry Padgett // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. (function (define) { define(['jquery'], function ($, root) { let opts_default = {}; root = root || {}; root.alert = alert; root.ajax = ajax; root.confirm = confirm; root.closeAjax = closeAjax; root.close = close; root.popUp = popUp; return root; function ajax(data) { let opts = { buttons: data.buttons, allowDrag: data.allowDrag, allowResize: data.allowResize, sizeHeight: data.sizeHeight, type: data.type, resolvePromiseOn: data.resolvePromiseOn, data: data.data, url: data.url, dataType: data.dataType // xml/json/text etc. }; let title = data.title; return dlgopen('', '', data.size, 0, '', title, opts); } function alert(data, title) { title = title ? title : 'Alert'; let alertTitle = ' ' + title + ''; return dlgopen('', '', 675, 0, '', alertTitle, { buttons: [ {text: 'OK', close: true, style: 'primary'} ], type: 'Alert', sizeHeight: 'auto', resolvePromiseOn: 'close', html: '

' + data + '

' }); } function confirm(data, title) { title = title ? title : 'Confirm'; let alertTitle = ' ' + title + ''; return dlgopen('', '', "modal-md", 0, '', alertTitle, { buttons: [ {text: 'Yes', close: true, id: 'confirmYes', style: 'primary'}, {text: 'No', close: true, id: 'confirmNo', style: 'primary'}, {text: 'Nevermind', close: true, style: 'secondary'} ], type: 'Confirm', resolvePromiseOn: 'confirm', sizeHeight: 'auto', html: '

' + data + '

' }); } /* popUp * Borrowed from a CKEditor Source plugin and modified to suit my purpose. * Licensed under the GPL 2 or greater. * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ function popUp( url, data, name, width, height) { width = width || '80%'; height = height || '85%'; if ( typeof width == 'string' && width.length > 1 && width.substr( width.length - 1, 1 ) === '%') { width = parseInt(window.screen.width * parseInt(width, 10) / 100, 10); } if ( typeof height == 'string' && height.length > 1 && height.substr( height.length - 1, 1 ) === '%') { height = parseInt(window.screen.height * parseInt(height, 10) / 100, 10); } if ( width < 640 ) { width = 640; } if ( height < 420 ) { height = 420; } let top = parseInt(( window.screen.height - height ) / 2, 10); let left = parseInt(( window.screen.width - width ) / 2, 10); let options = ('location=no,menubar=no,toolbar=no,dependent=yes,minimizable=no,modal=yes,alwaysRaised=yes,resizable=yes,scrollbars=yes') + ',width=' + width + ',height=' + height + ',top=' + top + ',left=' + left; let modalWindow = window.open('', name, options, true); if ( !modalWindow ) { return false; } try { modalWindow.focus(); if (data) { modalWindow.document.body.innerHTML = data; } else { modalWindow.location.href = url; } } catch ( e ) { window.open(url, null, options, true); } return true; } function closeAjax() { dlgCloseAjax(); } function close() { dlgclose(); } }); if (typeof includeScript !== 'function') { // Obviously utility.js has not been included for an unknown reason! // Will include below. function includeScript(srcUrl, type) { return new Promise(function (resolve, reject) { if (type == 'script') { let newScriptElement = document.createElement('script'); newScriptElement.src = srcUrl; newScriptElement.onload = () => resolve(newScriptElement); newScriptElement.onerror = () => reject(new Error(`Script load error for ${srcUrl}`)); document.head.append(newScriptElement); console.log('Needed to load:[' + srcUrl + '] For: [' + location + ']'); } if (type === "link") { let newScriptElement = document.createElement("link") newScriptElement.type = "text/css"; newScriptElement.rel = "stylesheet"; newScriptElement.href = srcUrl; newScriptElement.onload = () => resolve(newScriptElement); newScriptElement.onerror = () => reject(new Error(`Link load error for ${srcUrl}`)); document.head.append(newScriptElement); console.log('Needed to load:[' + srcUrl + '] For: [' + location + ']'); } }); } } if (typeof window.xl !== 'function') { (async (utilfn) => { await includeScript(utilfn, 'script'); })(top.webroot_url + '/library/js/utility.js') } }(typeof define == 'function' && define.amd ? define : function (args, mName) { this.dialog = typeof module != 'undefined' && module.exports ? mName(require(args[0], {}), module.exports) : mName(window.$); })); // open a new cascaded window function cascwin(url, winname, width, height, options) { var mywin = window.parent ? window.parent : window; var newx = 25, newy = 25; if (!isNaN(mywin.screenX)) { newx += mywin.screenX; newy += mywin.screenY; } else if (!isNaN(mywin.screenLeft)) { newx += mywin.screenLeft; newy += mywin.screenTop; } if ((newx + width) > screen.width || (newy + height) > screen.height) { newx = 0; newy = 0; } if (typeof top.restoreSession === 'function') { top.restoreSession(); } // MS IE version detection taken from // http://msdn2.microsoft.com/en-us/library/ms537509.aspx // to adjust the height of this box for IE only -- JRM if (navigator.appName == 'Microsoft Internet Explorer') { var ua = navigator.userAgent; var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); if (re.exec(ua) != null) rv = parseFloat(RegExp.$1); // this holds the version number height = height + 28; } retval = window.open(url, winname, options + ",width=" + width + ",height=" + height + ",left=" + newx + ",top=" + newy + ",screenX=" + newx + ",screenY=" + newy); return retval; } // recursive window focus-event grabber function grabfocus(w) { for (var i = 0; i < w.frames.length; ++i) grabfocus(w.frames[i]); w.onfocus = top.imfocused; } // Call this when a "modal" windowed dialog is desired. // Note that the below function is free standing for either // ui's.Use dlgopen() for responsive b.s modal dialogs. // Can now use anywhere to cascade natives...12/1/17 sjp // function dlgOpenWindow(url, winname, width, height) { if (top.modaldialog && !top.modaldialog.closed) { if (window.focus) top.modaldialog.focus(); if (top.modaldialog.confirm(top.oemr_dialog_close_msg)) { top.modaldialog.close(); top.modaldialog = null; } else { return false; } } top.modaldialog = cascwin(url, winname, width, height, "resizable=1,scrollbars=1,location=0,toolbar=0"); return false; } // This is called from del_related() which in turn is invoked by find_code_dynamic.php. // Deletes the specified codetype:code from the indicated input text element. function my_del_related(s, elem, usetitle) { if (!s) { // Deleting everything. elem.value = ''; if (usetitle) { elem.title = ''; } return; } // Convert the codes and their descriptions to arrays for easy manipulation. var acodes = elem.value.split(';'); var i = acodes.indexOf(s); if (i < 0) { return; // not found, should not happen } // Delete the indicated code and description and convert back to strings. acodes.splice(i, 1); elem.value = acodes.join(';'); if (usetitle) { var atitles = elem.title.split(';'); atitles.splice(i, 1); elem.title = atitles.join(';'); } } function dialogID() { function s4() { return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); } return s4() + s4() + s4() + s4() + s4() + +s4() + s4() + s4(); } // test for and/or remove dependency. function inDom(dependency, type, remove) { let el = type; let attr = type === 'script' ? 'src' : type === 'link' ? 'href' : 'none'; let all = document.getElementsByTagName(el); for (let i = all.length; i > -1; i--) { if (all[i] && all[i].getAttribute(attr) !== null && all[i].getAttribute(attr).indexOf(dependency) !== -1) { if (remove) { all[i].parentNode.removeChild(all[i]); console.log("Removed from DOM: " + dependency); return true; } else { return true; } } } return false; } // test to see if bootstrap theming is loaded (via standard or custom bootstrap library) // Will check for the badge-secondary class // - if exist, then assume bootstrap loaded // - if not exist, then assume bootstrap not loaded function isBootstrapCss() { for (let i = 0; i < document.styleSheets.length; i++) { let rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules; for (let x in rules) { if (rules[x].selectorText == '.badge-secondary') { return true; } } } return false; } // These functions may be called from scripts that may be out of scope with top so... // if opener is tab then we need to be in tabs UI scope and while we're at it, let's bring webroot along... // if (typeof top.webroot_url === "undefined" && opener) { if (typeof opener.top.webroot_url !== "undefined") { top.webroot_url = opener.top.webroot_url; } } // We'll need these if out of scope // if (typeof top.set_opener !== "function") { var opener_list = []; function set_opener(window, opener) { top.opener_list[window] = opener; } function get_opener(window) { return top.opener_list[window]; } } // universal alert popup message if (typeof alertMsg !== "function") { function alertMsg(message, timer = 5000, type = 'danger', size = '', persist = '') { // this xl() is just so cool. let gotIt = xl("Got It"); let title = xl("Alert"); let dismiss = xl("Dismiss"); $('#alert_box').remove(); let oHidden = ''; oHidden = !persist ? "hidden" : ''; let oSize = (size == 'lg') ? 'left:10%;width:80%;' : 'left:25%;width:50%;'; let style = "position:fixed;top:25%;" + oSize + " bottom:0;z-index:9999;"; $("body").prepend("
"); let mHtml = '
' + '' + '

' + title + '!


' + '

' + message + '

' + '
'; $('#alert_box').append(mHtml); $('#alertmsg').on('closed.bs.alert', function () { clearTimeout(AlertMsg); $('#alert_box').remove(); return false; }); $('#dontShowAgain').on('click', function (e) { persistUserOption(persist, 1); }); let AlertMsg = setTimeout(function () { $('#alertmsg').fadeOut(800, function () { $('#alert_box').remove(); }); }, timer); } const persistUserOption = function (option, value) { return $.ajax({ url: top.webroot_url + "/library/ajax/user_settings.php", type: 'post', contentType: 'application/x-www-form-urlencoded', data: { csrf_token_form: top.csrf_token_js, target: option, setting: value }, beforeSend: function () { if (typeof top.restoreSession === 'function') { top.restoreSession(); } }, error: function (jqxhr, status, errorThrown) { console.log(errorThrown); } }); }; } // Test if supporting dialog callbacks and close dependencies are in scope. // This is useful when opening and closing the dialog is in the same scope. Still use include_opener.js // in script that will close a dialog that is not in the same scope dlgopen was used // or use parent.dlgclose() if known decedent. // dlgopen() will always have a name whether assigned by dev or created by function. // Callback, onClosed and button clicks are still available either way. // For a callback on close use: dlgclose(functionName, farg1, farg2 ...) which becomes: functionName(farg1,farg2, etc) // if (typeof dlgclose !== "function") { if (!opener) { opener = window; } function dlgclose(call, args) { var frameName = window.name; var wframe = top; if (frameName === '') { // try to find dialog. dialogModal is embedded dialog class // It has to be here somewhere. frameName = $(".dialogModal").attr('id'); if (!frameName) { frameName = parent.$(".dialogModal").attr('id'); if (!frameName) { console.log("Unable to find dialog."); return false; } } } var dialogModal = top.$('div#' + frameName); var removeFrame = dialogModal.find("iframe[name='" + frameName + "']"); if (removeFrame.length > 0) { removeFrame.remove(); } if (dialogModal.length > 0) { if (call) { wframe.setCallBack(call, args); // sets/creates callback function in dialogs scope. } dialogModal.modal('hide'); } else { // no opener not iframe must be in here $(this.document).find(".dialogModal").modal('hide'); } } } /* * function dlgopen(url, winname, width, height, forceNewWindow, title, opts) * * @summary Stackable, resizable and draggable responsive ajax/iframe dialog modal. * * @param {url} string Content location. * @param {String} winname If set becomes modal id and/or iframes name. Or, one is created/assigned(iframes). * @param {Number| String} width|modalSize(modal-xl) For sizing: an number will be converted to a percentage of view port width. * @param {Number} height Initial minimum height. For iframe auto resize starts at this height. * @param {boolean} forceNewWindow Force using a native window. * @param {String} title If exist then header with title is created otherwise no header and content only. * @param {Object} opts Dialogs options. * @returns {Object} dialog object reference. * */ function dlgopen(url, winname, width, height, forceNewWindow, title, opts) { // First things first... if (typeof top.restoreSession === 'function') { top.restoreSession(); } // A matter of Legacy if (forceNewWindow) { return dlgOpenWindow(url, winname, width, height); } // wait for DOM then check dependencies needed to run this feature. // dependency duration is while 'this' is in scope, temporary... // seldom will this get used as more of U.I is moved to Bootstrap // but better to continue than stop because of a dependency... // let jqurl = top.webroot_url + '/public/assets/jquery/dist/jquery.min.js'; if (typeof jQuery === 'undefined') { (async (utilfn) => { await includeScript(utilfn, 'script'); })(jqurl); } jQuery(function () { // Check for dependencies we will need. // webroot_url is a global defined in main_screen.php or main.php. let bscss = top.webroot_url + '/public/assets/bootstrap/dist/css/bootstrap.min.css'; let bscssRtl = top.webroot_url + '/public/assets/bootstrap-v4-rtl/dist/css/bootstrap-rtl.min.css'; let bsurl = top.webroot_url + '/public/assets/bootstrap/dist/js/bootstrap.bundle.min.js'; let version = jQuery.fn.jquery.split(' ')[0].split('.'); if ((version[0] < 2 && version[1] < 9) || (version[0] === 1 && version[1] === 9 && version[2] < 1)) { inDom('jquery-min', 'script', true); (async (utilfn) => { await includeScript(utilfn, 'script'); })(jqurl).then(() => { console.log('Replacing jQuery version:[ ' + version + ' ]'); }); } if (!isBootstrapCss()) { (async (utilfn) => { await includeScript(utilfn, 'link'); })(bscss); if (top.jsLanguageDirection == 'rtl') { (async (utilfn) => { await includeScript(utilfn, 'link'); })(bscssRtl); } } if (typeof jQuery.fn.modal === 'undefined') { if (!inDom('bootstrap.bundle.min.js', 'script', false)) { (async (utilfn) => { await includeScript(utilfn, 'script'); })(bsurl); } } }); // onward var opts_defaults = { type: 'iframe', // POST, GET (ajax) or iframe async: true, frameContent: "", // for iframe embedded content html: "", // content for alerts, comfirm etc ajax allowDrag: false, allowResize: true, sizeHeight: 'auto', // 'full' will use as much height as allowed // use is onClosed: fnName ... args not supported however, onClosed: 'reload' is auto defined and requires no function to be created. onClosed: false, allowExternal: false, // allow a dialog window to a URL that is external to the current url callBack: false, // use {call: 'functionName, args: args, args} if known or use dlgclose. resolvePromiseOn: '' // this may be useful. values are init, shown, show, confirm, alert and close which coincide with dialog events. }; if (!opts) { opts = {}; } opts = jQuery.extend({}, opts_defaults, opts); opts.type = opts.type ? opts.type.toLowerCase() : ''; opts.resolvePromiseOn = opts.resolvePromiseOn ?? 'init'; var mHeight, mWidth, mSize, msSize, dlgContainer, fullURL, where; // a growing list... where = (opts.type === 'iframe') ? top : window; // get url straight... fullURL = ""; if (opts.url) { url = opts.url; } if (url) { if (url[0] === "/") { fullURL = url } else if (opts.allowExternal === true) { var checkUrl = new URL(url); // we only allow http & https protocols to be launched if (checkUrl.protocol === "http:" || checkUrl.protocol == "https:") { fullURL = url; } } else { fullURL = window.location.href.substr(0, window.location.href.lastIndexOf("/") + 1) + url; } } // what's a window without a name. important for stacking and opener. winname = (winname === "_blank" || !winname) ? dialogID() : winname; // for small screens or request width is larger than viewport. if (where.innerWidth <= 1080) { width = "modal-full"; } // Convert dialog size to percentages and/or css class. var sizeChoices = ['modal-sm', 'modal-md', 'modal-mlg', 'modal-lg', 'modal-xl', 'modal-full']; if (Math.abs(width) > 0) { width = Math.abs(width); mWidth = (width / where.innerWidth * 100).toFixed(1) + '%'; msSize = ''; mSize = 'modal-custom' + winname; } else if (jQuery.inArray(width, sizeChoices) !== -1) { mSize = width; // is a modal class } else { msSize = ''; // standard B.S. modal default (modal-md) } // leave below for legacy if (mSize === 'modal-sm') { msSize = ''; } else if (mSize === 'modal-md') { msSize = ''; } else if (mSize === 'modal-mlg') { msSize = ''; } else if (mSize === 'modal-lg') { msSize = ''; } else if (mSize === 'modal-xl') { msSize = ''; } else if (mSize === 'modal-full') { msSize = ''; } mSize = 'modal-custom-' + winname; // Initial responsive height. let vpht = where.innerHeight; if (height <= 300 && opts.type === 'iframe') { height = 300; } mHeight = height > 0 ? (height / vpht * 100).toFixed(1) + 'vh' : ''; // Build modal template. For now !title = !header and modal full height. var mTitle = title > "" ? '' : ''; var waitHtml = '
' + '' + '
'; var headerhtml = ('').replace('%title%', mTitle); var frameHtml = ('').replace('%winname%', winname).replace('%url%', fullURL ? 'src=' + fullURL : ''); var contentStyles = ('style="height:%initHeight%; max-height: 94vh"').replace('%initHeight%', opts.sizeHeight !== 'full' ? mHeight : '90vh'); var altClose = '
'; var mhtml = ('').replace('%id%', winname).replace('%sizeStyle%', msSize ? msSize : '').replace('%dialogId%', opts.dialogId ? ('id=' + opts.dialogId + '"') : '').replace('%sizeClass%', mSize ? mSize : '').replace('%head%', mTitle !== '' ? headerhtml : '').replace('%altclose%', mTitle === '' ? altClose : '').replace('%drag-action%', (opts.allowDrag) ? 'drag-action' : '').replace('%resize-action%', (opts.allowResize) ? 'resize-action' : '').replace('%wait%', '').replace('%contentStyles%', contentStyles).replace('%body%', opts.type === 'iframe' ? frameHtml : ''); // Write modal template. dlgContainer = where.jQuery(mhtml); dlgContainer.attr("name", winname); // No url and just iframe content if (opts.frameContent && opts.type === 'iframe') { var ipath = 'data:text/html,' + encodeURIComponent(opts.frameContent); dlgContainer.find("iframe[name='" + winname + "']").attr("src", ipath); } if (opts.buttons) { dlgContainer.find('.modal-content').append(buildFooter()); } // Ajax setup if (opts.type === 'alert') { dlgContainer.find('.modal-body').html(opts.html); } if (opts.type === 'confirm') { dlgContainer.find('.modal-body').html(opts.html); } if (opts.type !== 'iframe' && opts.type !== 'alert' && opts.type !== 'confirm') { var params = { async: opts.async, method: opts.type || '', // if empty and has data object, then post else get. content: opts.data || opts.html, // ajax loads fetched content. url: opts.url || fullURL, dataType: opts.dataType || '' // xml/json/text etc. }; dialogAjax(params, dlgContainer, opts); } // let opener array know about us. top.set_opener(winname, window); // Write the completed template to calling document or 'where' window. where.jQuery("body").append(dlgContainer); // We promised return new Promise((resolve, reject) => { jQuery(function () { // DOM Ready. Handle events and cleanup. if (opts.type === 'iframe') { var modalwin = where.jQuery('body').find("[name='" + winname + "']"); jQuery('div.modal-dialog', modalwin).css({'margin': "0.75rem auto auto"}); modalwin.on('load', function (e) { setTimeout(function () { if (opts.sizeHeight === 'auto' && opts.type === 'iframe') { SizeModaliFrame(e, height); } else if (opts.sizeHeight === 'fixed') { sizing(e, height); } else { sizing(e, height); // must be full height of container } }, 800); }); } else { var modalwin = where.jQuery('body').find("[name='" + winname + "']"); jQuery('div.modal-dialog', modalwin).css({'margin': '15px auto auto'}); modalwin.on('show.bs.modal', function (e) { setTimeout(function () { sizing(e, height); }, 800); }); } if (opts.resolvePromiseOn === 'confirm') { jQuery("#confirmYes").on('click', function (e) { resolve(true); }); jQuery("#confirmNo").on('click', function (e) { resolve(false); }); } // events chain. dlgContainer.on('show.bs.modal', function () { if (opts.allowResize || opts.allowDrag) { initDragResize(where.document, where.document); } if (opts.resolvePromiseOn === 'show') { resolve(dlgContainer); } }).on('shown.bs.modal', function () { // Remove waitHtml spinner/loader etc. jQuery(this).parent().find('div.loadProgress').fadeOut(function () { jQuery(this).remove(); }); dlgContainer.modal('handleUpdate'); // allow for scroll bar if (opts.resolvePromiseOn === 'shown') { resolve(dlgContainer); } }).on('hidden.bs.modal', function (e) { // clear cursor e.target.style.cursor = "pointer"; // remove our dialog jQuery(this).remove(); // now we can run functions in our window. if (opts.onClosed) { console.log('Doing onClosed:[' + opts.onClosed + ']'); if (opts.onClosed === 'reload') { window.location.reload(); } else { window[opts.onClosed](); } } if (opts.callBack.call) { console.log('Doing callBack:[' + opts.callBack.call + '|' + opts.callBack.args + ']'); if (opts.callBack.call === 'reload') { window.location.reload(); } else if (typeof opts.callBack.call == 'string') { window[opts.callBack.call](opts.callBack.args); } else { opts.callBack.call(opts.callBack.args); } } if (opts.resolvePromiseOn == 'close') { resolve(dlgContainer); } }); // define local dialog close() function. openers scope window.dlgCloseAjax = function (calling, args) { if (calling) { opts.callBack = {call: calling, args: args}; } dlgContainer.modal('hide'); // important to clean up in only one place, hide event.... return false; }; // define local callback function. Set with opener or from opener, will exe on hide. window.dlgSetCallBack = function (calling, args) { opts.callBack = {call: calling, args: args}; return false; }; // in residents dialog scope where.setCallBack = function (calling, args) { opts.callBack = {call: calling, args: args}; return true; }; where.getOpener = function () { return where; }; // dialog is completely built and events set // this is default returning our dialog container reference. if (opts.resolvePromiseOn == 'init') { resolve(dlgContainer); } // Finally Show Dialog after DOM settles dlgContainer.modal({backdrop: 'static', keyboard: true}, 'show'); }); // end events }); /* Returning Promise */ // Ajax call with promise via dialog function dialogAjax(data, $dialog, opts) { var params = { async: data.async, method: data.method || '', data: data.content, url: data.url, dataType: data.dataType || 'html' }; if (data.url) { jQuery.extend(params, data); } jQuery.ajax(params).done(aOkay).fail(oops); return true; function aOkay(html) { opts.ajax = true; $dialog.find('.modal-body').html(data.success ? data.success(html) : html); return true; } function oops(r, s) { var msg = data.error ? data.error(r, s, params) : '
' + 'XHR Failed: [ ' + params.url + '].' + '
'; $dialog.find('.modal-body').html(msg); return false; } } function buildFooter() { if (opts.buttons === false) { return ''; } var oFoot = jQuery('
').addClass('modal-footer').prop('id', 'oefooter'); if (opts.buttons) { for (var i = 0, k = opts.buttons.length; i < k; i++) { var btnOp = opts.buttons[i]; if (typeof btnOp.class !== 'undefined') { btnOp.class = btnOp.class.replace(/default/gi, 'secondary'); var btn = jQuery('