UNPKG

alertifyjs

Version:

AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.

1,315 lines (1,225 loc) 144 kB
/** * alertifyjs 1.14.0 http://alertifyjs.com * AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications. * Copyright 2024 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com) * Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/ ( function ( window ) { 'use strict'; var NOT_DISABLED_NOT_RESET = ':not(:disabled):not(.ajs-reset)'; /** * Keys enum * @type {Object} */ var keys = { ENTER: 13, ESC: 27, F1: 112, F12: 123, LEFT: 37, RIGHT: 39, TAB: 9 }; /** * Default options * @type {Object} */ var defaults = { autoReset:true, basic:false, closable:true, closableByDimmer:true, invokeOnCloseOff:false, frameless:false, defaultFocusOff:false, maintainFocus:true, //global default not per instance, applies to all dialogs maximizable:true, modal:true, movable:true, moveBounded:false, overflow:true, padding: true, pinnable:true, pinned:true, preventBodyShift:false, //global default not per instance, applies to all dialogs resizable:true, startMaximized:false, transition:'pulse', transitionOff:false, tabbable:['button', '[href]', 'input', 'select', 'textarea', '[tabindex]:not([tabindex^="-"])'+NOT_DISABLED_NOT_RESET].join(NOT_DISABLED_NOT_RESET+','),//global notifier:{ delay:5, position:'bottom-right', closeButton:false, classes: { base: 'alertify-notifier', prefix:'ajs-', message: 'ajs-message', top: 'ajs-top', right: 'ajs-right', bottom: 'ajs-bottom', left: 'ajs-left', center: 'ajs-center', visible: 'ajs-visible', hidden: 'ajs-hidden', close: 'ajs-close' } }, glossary:{ title:'AlertifyJS', ok: 'OK', cancel: 'Cancel', acccpt: 'Accept', deny: 'Deny', confirm: 'Confirm', decline: 'Decline', close: 'Close', maximize: 'Maximize', restore: 'Restore', }, theme:{ input:'ajs-input', ok:'ajs-ok', cancel:'ajs-cancel', }, hooks:{ preinit:function(){}, postinit:function(){} } }; //holds open dialogs instances var openDialogs = []; /** * [Helper] Adds the specified class(es) to the element. * * @element {node} The element * @className {string} One or more space-separated classes to be added to the class attribute of the element. * * @return {undefined} */ function addClass(element,classNames){ element.className += ' ' + classNames; } /** * [Helper] Removes the specified class(es) from the element. * * @element {node} The element * @className {string} One or more space-separated classes to be removed from the class attribute of the element. * * @return {undefined} */ function removeClass(element, classNames) { var original = element.className.split(' '); var toBeRemoved = classNames.split(' '); for (var x = 0; x < toBeRemoved.length; x += 1) { var index = original.indexOf(toBeRemoved[x]); if (index > -1){ original.splice(index,1); } } element.className = original.join(' '); } /** * [Helper] Checks if the document is RTL * * @return {Boolean} True if the document is RTL, false otherwise. */ function isRightToLeft(){ return window.getComputedStyle(document.body).direction === 'rtl'; } /** * [Helper] Get the document current scrollTop * * @return {Number} current document scrollTop value */ function getScrollTop(){ return ((document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop); } /** * [Helper] Get the document current scrollLeft * * @return {Number} current document scrollLeft value */ function getScrollLeft(){ return ((document.documentElement && document.documentElement.scrollLeft) || document.body.scrollLeft); } /** * Helper: clear contents * */ function clearContents(element){ while (element.lastChild) { element.removeChild(element.lastChild); } } /** * detects strings, checks for both string and String instances * this is unlike typeof(x) === 'string' which only accepts primitive strings * */ function isString(thing) { return Object.prototype.toString.call(thing) === '[object String]'; } /** * Extends a given prototype by merging properties from base into sub. * * @sub {Object} sub The prototype being overwritten. * @base {Object} base The prototype being written. * * @return {Object} The extended prototype. */ function copy(src) { if(null === src){ return src; } var cpy; if(Array.isArray(src)){ cpy = []; for(var x=0;x<src.length;x+=1){ cpy.push(copy(src[x])); } return cpy; } if(src instanceof Date){ return new Date(src.getTime()); } if(src instanceof RegExp){ cpy = new RegExp(src.source); cpy.global = src.global; cpy.ignoreCase = src.ignoreCase; cpy.multiline = src.multiline; cpy.lastIndex = src.lastIndex; return cpy; } if(typeof src === 'object'){ cpy = {}; // copy dialog pototype over definition. for (var prop in src) { if (src.hasOwnProperty(prop)) { cpy[prop] = copy(src[prop]); } } return cpy; } return src; } /** * Helper: destruct the dialog * */ function destruct(instance, initialize){ if(instance.elements){ //delete the dom and it's references. var root = instance.elements.root; root.parentNode.removeChild(root); delete instance.elements; //copy back initial settings. instance.settings = copy(instance.__settings); //re-reference init function. instance.__init = initialize; //delete __internal variable to allow re-initialization. delete instance.__internal; } } /** * Test to check if passive event listeners are supported. */ var IsPassiveSupported = false; try { var options = Object.defineProperty({}, 'passive', { get: function () { IsPassiveSupported = true; } }); window.addEventListener('test', options, options); window.removeEventListener('test', options, options); } catch (e) {} /** * Removes an event listener * * @param {HTMLElement} el The EventTarget to register the listenr on. * @param {string} event The event type to listen for. * @param {Function} handler The function to handle the event. * @param {boolean} useCapture Specifices if the event to be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. * @param {boolean} passive A Boolean which, if true, indicates that the function specified by listener will never call preventDefault(). */ var on = function (el, event, fn, useCapture, passive) { el.addEventListener(event, fn, IsPassiveSupported ? { capture: useCapture, passive: passive } : useCapture === true); }; /** * Removes an event listener * * @param {HTMLElement} el The EventTarget to unregister the listenr from. * @param {string} event The event type to remove. * @param {Function} fn The event handler to remove. * @param {boolean} useCapture Specifices if the event to be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. * @param {boolean} passive A Boolean which, if true, indicates that the function specified by listener will never call preventDefault(). */ var off = function (el, event, fn, useCapture, passive) { el.removeEventListener(event, fn, IsPassiveSupported ? { capture: useCapture, passive: passive } : useCapture === true); }; /** * Prevent default event from firing * * @param {Event} event Event object * @return {undefined} function prevent ( event ) { if ( event ) { if ( event.preventDefault ) { event.preventDefault(); } else { event.returnValue = false; } } } */ var transition = (function () { var t, type; var supported = false; var transitions = { 'animation' : 'animationend', 'OAnimation' : 'oAnimationEnd oanimationend', 'msAnimation' : 'MSAnimationEnd', 'MozAnimation' : 'animationend', 'WebkitAnimation' : 'webkitAnimationEnd' }; for (t in transitions) { if (document.documentElement.style[t] !== undefined) { type = transitions[t]; supported = true; break; } } return { type: type, supported: supported }; }()); /** * Creates event handler delegate that sends the instance as last argument. * * @return {Function} a function wrapper which sends the instance as last argument. */ function delegate(context, method) { return function () { if (arguments.length > 0) { var args = []; for (var x = 0; x < arguments.length; x += 1) { args.push(arguments[x]); } args.push(context); return method.apply(context, args); } return method.apply(context, [null, context]); }; } /** * Helper for creating a dialog close event. * * @return {object} */ function createCloseEvent(index, button) { return { index: index, button: button, cancel: false }; } /** * Helper for dispatching events. * * @param {string} evenType The type of the event to disptach. * @param {object} instance The dialog instance disptaching the event. * * @return {any} The result of the invoked function. */ function dispatchEvent(eventType, instance) { if ( typeof instance.get(eventType) === 'function' ) { return instance.get(eventType).call(instance); } } /** * Super class for all dialogs * * @return {Object} base dialog prototype */ var dialog = (function () { var //holds the list of used keys. usedKeys = [], //dummy variable, used to trigger dom reflow. reflow = null, //holds body tab index in case it has any. tabindex = false, //condition for detecting safari isSafari = window.navigator.userAgent.indexOf('Safari') > -1 && window.navigator.userAgent.indexOf('Chrome') < 0, //dialog building blocks templates = { dimmer:'<div class="ajs-dimmer"></div>', /*tab index required to fire click event before body focus*/ modal: '<div class="ajs-modal" tabindex="0"></div>', dialog: '<div class="ajs-dialog" tabindex="0"></div>', reset: '<button class="ajs-reset"></button>', commands: '<div class="ajs-commands"><button class="ajs-pin"></button><button class="ajs-maximize"></button><button class="ajs-close"></button></div>', header: '<div class="ajs-header"></div>', body: '<div class="ajs-body"></div>', content: '<div class="ajs-content"></div>', footer: '<div class="ajs-footer"></div>', buttons: { primary: '<div class="ajs-primary ajs-buttons"></div>', auxiliary: '<div class="ajs-auxiliary ajs-buttons"></div>' }, button: '<button class="ajs-button"></button>', resizeHandle: '<div class="ajs-handle"></div>', }, //common class names classes = { animationIn: 'ajs-in', animationOut: 'ajs-out', base: 'alertify', basic:'ajs-basic', capture: 'ajs-capture', closable:'ajs-closable', fixed: 'ajs-fixed', frameless:'ajs-frameless', hidden: 'ajs-hidden', maximize: 'ajs-maximize', maximized: 'ajs-maximized', maximizable:'ajs-maximizable', modeless: 'ajs-modeless', movable: 'ajs-movable', noSelection: 'ajs-no-selection', noOverflow: 'ajs-no-overflow', noPadding:'ajs-no-padding', pin:'ajs-pin', pinnable:'ajs-pinnable', prefix: 'ajs-', resizable: 'ajs-resizable', restore: 'ajs-restore', shake:'ajs-shake', unpinned:'ajs-unpinned', noTransition:'ajs-no-transition' }; /** * Helper: initializes the dialog instance * * @return {Number} The total count of currently open modals. */ function initialize(instance){ if(!instance.__internal){ //invoke preinit global hook alertify.defaults.hooks.preinit(instance); //no need to expose init after this. delete instance.__init; //keep a copy of initial dialog settings if(!instance.__settings){ instance.__settings = copy(instance.settings); } //get dialog buttons/focus setup var setup; if(typeof instance.setup === 'function'){ setup = instance.setup(); setup.options = setup.options || {}; setup.focus = setup.focus || {}; }else{ setup = { buttons:[], focus:{ element:null, select:false }, options:{ } }; } //initialize hooks object. if(typeof instance.hooks !== 'object'){ instance.hooks = {}; } //copy buttons defintion var buttonsDefinition = []; if(Array.isArray(setup.buttons)){ for(var b=0;b<setup.buttons.length;b+=1){ var ref = setup.buttons[b], cpy = {}; for (var i in ref) { if (ref.hasOwnProperty(i)) { cpy[i] = ref[i]; } } buttonsDefinition.push(cpy); } } var internal = instance.__internal = { /** * Flag holding the open state of the dialog * * @type {Boolean} */ isOpen:false, /** * Active element is the element that will receive focus after * closing the dialog. It defaults as the body tag, but gets updated * to the last focused element before the dialog was opened. * * @type {Node} */ activeElement:document.body, timerIn:undefined, timerOut:undefined, buttons: buttonsDefinition, focus: setup.focus, options: { title: undefined, modal: undefined, basic:undefined, frameless:undefined, defaultFocusOff:undefined, pinned: undefined, movable: undefined, moveBounded:undefined, resizable: undefined, autoReset: undefined, closable: undefined, closableByDimmer: undefined, invokeOnCloseOff:undefined, maximizable: undefined, startMaximized: undefined, pinnable: undefined, transition: undefined, transitionOff: undefined, padding:undefined, overflow:undefined, onshow:undefined, onclosing:undefined, onclose:undefined, onfocus:undefined, onmove:undefined, onmoved:undefined, onresize:undefined, onresized:undefined, onmaximize:undefined, onmaximized:undefined, onrestore:undefined, onrestored:undefined }, resetHandler:undefined, beginMoveHandler:undefined, beginResizeHandler:undefined, bringToFrontHandler:undefined, modalClickHandler:undefined, buttonsClickHandler:undefined, commandsClickHandler:undefined, transitionInHandler:undefined, transitionOutHandler:undefined, destroy:undefined }; var elements = {}; //root node elements.root = document.createElement('div'); //prevent FOUC in case of async styles loading. elements.root.style.display = 'none'; elements.root.className = classes.base + ' ' + classes.hidden + ' '; elements.root.innerHTML = templates.dimmer + templates.modal; //dimmer elements.dimmer = elements.root.firstChild; //dialog elements.modal = elements.root.lastChild; elements.modal.innerHTML = templates.dialog; elements.dialog = elements.modal.firstChild; elements.dialog.innerHTML = templates.reset + templates.commands + templates.header + templates.body + templates.footer + templates.resizeHandle + templates.reset; //reset links elements.reset = []; elements.reset.push(elements.dialog.firstChild); elements.reset.push(elements.dialog.lastChild); //commands elements.commands = {}; elements.commands.container = elements.reset[0].nextSibling; elements.commands.pin = elements.commands.container.firstChild; elements.commands.maximize = elements.commands.pin.nextSibling; elements.commands.close = elements.commands.maximize.nextSibling; //header elements.header = elements.commands.container.nextSibling; //body elements.body = elements.header.nextSibling; elements.body.innerHTML = templates.content; elements.content = elements.body.firstChild; //footer elements.footer = elements.body.nextSibling; elements.footer.innerHTML = templates.buttons.auxiliary + templates.buttons.primary; //resize handle elements.resizeHandle = elements.footer.nextSibling; //buttons elements.buttons = {}; elements.buttons.auxiliary = elements.footer.firstChild; elements.buttons.primary = elements.buttons.auxiliary.nextSibling; elements.buttons.primary.innerHTML = templates.button; elements.buttonTemplate = elements.buttons.primary.firstChild; //remove button template elements.buttons.primary.removeChild(elements.buttonTemplate); for(var x=0; x < instance.__internal.buttons.length; x+=1) { var button = instance.__internal.buttons[x]; // add to the list of used keys. if(usedKeys.indexOf(button.key) < 0){ usedKeys.push(button.key); } button.element = elements.buttonTemplate.cloneNode(); button.element.innerHTML = button.text; if(typeof button.className === 'string' && button.className !== ''){ addClass(button.element, button.className); } for(var key in button.attrs){ if(key !== 'className' && button.attrs.hasOwnProperty(key)){ button.element.setAttribute(key, button.attrs[key]); } } if(button.scope === 'auxiliary'){ elements.buttons.auxiliary.appendChild(button.element); }else{ elements.buttons.primary.appendChild(button.element); } } //make elements pubic instance.elements = elements; //save event handlers delegates internal.resetHandler = delegate(instance, onReset); internal.beginMoveHandler = delegate(instance, beginMove); internal.beginResizeHandler = delegate(instance, beginResize); internal.bringToFrontHandler = delegate(instance, bringToFront); internal.modalClickHandler = delegate(instance, modalClickHandler); internal.buttonsClickHandler = delegate(instance, buttonsClickHandler); internal.commandsClickHandler = delegate(instance, commandsClickHandler); internal.transitionInHandler = delegate(instance, handleTransitionInEvent); internal.transitionOutHandler = delegate(instance, handleTransitionOutEvent); //settings for(var opKey in internal.options){ if(setup.options[opKey] !== undefined){ // if found in user options instance.set(opKey, setup.options[opKey]); }else if(alertify.defaults.hasOwnProperty(opKey)) { // else if found in defaults options instance.set(opKey, alertify.defaults[opKey]); }else if(opKey === 'title' ) { // else if title key, use alertify.defaults.glossary instance.set(opKey, alertify.defaults.glossary[opKey]); } } // allow dom customization if(typeof instance.build === 'function'){ instance.build(); } //invoke postinit global hook alertify.defaults.hooks.postinit(instance); } //add to the end of the DOM tree. document.body.appendChild(instance.elements.root); } /** * Helper: maintains scroll position * */ var scrollX, scrollY; function saveScrollPosition(){ scrollX = getScrollLeft(); scrollY = getScrollTop(); } function restoreScrollPosition(){ window.scrollTo(scrollX, scrollY); } /** * Helper: adds/removes no-overflow class from body * */ function ensureNoOverflow(){ var requiresNoOverflow = 0; for(var x=0;x<openDialogs.length;x+=1){ var instance = openDialogs[x]; if(instance.isModal() || instance.isMaximized()){ requiresNoOverflow+=1; } } if(requiresNoOverflow === 0 && document.body.className.indexOf(classes.noOverflow) >= 0){ //last open modal or last maximized one removeClass(document.body, classes.noOverflow); preventBodyShift(false); }else if(requiresNoOverflow > 0 && document.body.className.indexOf(classes.noOverflow) < 0){ //first open modal or first maximized one preventBodyShift(true); addClass(document.body, classes.noOverflow); } } var top = '', topScroll = 0; /** * Helper: prevents body shift. * */ function preventBodyShift(add){ if(alertify.defaults.preventBodyShift){ if(add && document.documentElement.scrollHeight > document.documentElement.clientHeight ){//&& openDialogs[openDialogs.length-1].elements.dialog.clientHeight <= document.documentElement.clientHeight){ topScroll = scrollY; top = window.getComputedStyle(document.body).top; addClass(document.body, classes.fixed); document.body.style.top = -scrollY + 'px'; } else if(!add) { scrollY = topScroll; document.body.style.top = top; removeClass(document.body, classes.fixed); restoreScrollPosition(); } } } /** * Sets the name of the transition used to show/hide the dialog * * @param {Object} instance The dilog instance. * */ function updateTransition(instance, value, oldValue){ if(isString(oldValue)){ removeClass(instance.elements.root,classes.prefix + oldValue); } addClass(instance.elements.root, classes.prefix + value); reflow = instance.elements.root.offsetWidth; } /** * Toggles the dialog no transition * * @param {Object} instance The dilog instance. * * @return {undefined} */ function updateTransitionOff(instance){ if (instance.get('transitionOff')) { // add class addClass(instance.elements.root, classes.noTransition); } else { // remove class removeClass(instance.elements.root, classes.noTransition); } } /** * Toggles the dialog display mode * * @param {Object} instance The dilog instance. * * @return {undefined} */ function updateDisplayMode(instance){ if(instance.get('modal')){ //make modal removeClass(instance.elements.root, classes.modeless); //only if open if(instance.isOpen()){ unbindModelessEvents(instance); //in case a pinned modless dialog was made modal while open. updateAbsPositionFix(instance); ensureNoOverflow(); } }else{ //make modelss addClass(instance.elements.root, classes.modeless); //only if open if(instance.isOpen()){ bindModelessEvents(instance); //in case pin/unpin was called while a modal is open updateAbsPositionFix(instance); ensureNoOverflow(); } } } /** * Toggles the dialog basic view mode * * @param {Object} instance The dilog instance. * * @return {undefined} */ function updateBasicMode(instance){ if (instance.get('basic')) { // add class addClass(instance.elements.root, classes.basic); } else { // remove class removeClass(instance.elements.root, classes.basic); } } /** * Toggles the dialog frameless view mode * * @param {Object} instance The dilog instance. * * @return {undefined} */ function updateFramelessMode(instance){ if (instance.get('frameless')) { // add class addClass(instance.elements.root, classes.frameless); } else { // remove class removeClass(instance.elements.root, classes.frameless); } } /** * Helper: Brings the modeless dialog to front, attached to modeless dialogs. * * @param {Event} event Focus event * @param {Object} instance The dilog instance. * * @return {undefined} */ function bringToFront(event, instance){ // Do not bring to front if preceeded by an open modal var index = openDialogs.indexOf(instance); for(var x=index+1;x<openDialogs.length;x+=1){ if(openDialogs[x].isModal()){ return; } } // Bring to front by making it the last child. if(document.body.lastChild !== instance.elements.root){ document.body.appendChild(instance.elements.root); //also make sure its at the end of the list openDialogs.splice(openDialogs.indexOf(instance),1); openDialogs.push(instance); setFocus(instance); } return false; } /** * Helper: reflects dialogs options updates * * @param {Object} instance The dilog instance. * @param {String} option The updated option name. * * @return {undefined} */ function optionUpdated(instance, option, oldValue, newValue){ switch(option){ case 'title': instance.setHeader(newValue); break; case 'modal': updateDisplayMode(instance); break; case 'basic': updateBasicMode(instance); break; case 'frameless': updateFramelessMode(instance); break; case 'pinned': updatePinned(instance); break; case 'closable': updateClosable(instance); break; case 'maximizable': updateMaximizable(instance); break; case 'pinnable': updatePinnable(instance); break; case 'movable': updateMovable(instance); break; case 'resizable': updateResizable(instance); break; case 'padding': if(newValue){ removeClass(instance.elements.root, classes.noPadding); }else if(instance.elements.root.className.indexOf(classes.noPadding) < 0){ addClass(instance.elements.root, classes.noPadding); } break; case 'overflow': if(newValue){ removeClass(instance.elements.root, classes.noOverflow); }else if(instance.elements.root.className.indexOf(classes.noOverflow) < 0){ addClass(instance.elements.root, classes.noOverflow); } break; case 'transition': updateTransition(instance,newValue, oldValue); break; case 'transitionOff': updateTransitionOff(instance); break; } // internal on option updated event if(typeof instance.hooks.onupdate === 'function'){ instance.hooks.onupdate.call(instance, option, oldValue, newValue); } } /** * Helper: reflects dialogs options updates * * @param {Object} instance The dilog instance. * @param {Object} obj The object to set/get a value on/from. * @param {Function} callback The callback function to call if the key was found. * @param {String|Object} key A string specifying a propery name or a collection of key value pairs. * @param {Object} value Optional, the value associated with the key (in case it was a string). * @param {String} option The updated option name. * * @return {Object} result object * The result objects has an 'op' property, indicating of this is a SET or GET operation. * GET: * - found: a flag indicating if the key was found or not. * - value: the property value. * SET: * - items: a list of key value pairs of the properties being set. * each contains: * - found: a flag indicating if the key was found or not. * - key: the property key. * - value: the property value. */ function update(instance, obj, callback, key, value){ var result = {op:undefined, items: [] }; if(typeof value === 'undefined' && typeof key === 'string') { //get result.op = 'get'; if(obj.hasOwnProperty(key)){ result.found = true; result.value = obj[key]; }else{ result.found = false; result.value = undefined; } } else { var old; //set result.op = 'set'; if(typeof key === 'object'){ //set multiple var args = key; for (var prop in args) { if (obj.hasOwnProperty(prop)) { if(obj[prop] !== args[prop]){ old = obj[prop]; obj[prop] = args[prop]; callback.call(instance,prop, old, args[prop]); } result.items.push({ 'key': prop, 'value': args[prop], 'found':true}); }else{ result.items.push({ 'key': prop, 'value': args[prop], 'found':false}); } } } else if (typeof key === 'string'){ //set single if (obj.hasOwnProperty(key)) { if(obj[key] !== value){ old = obj[key]; obj[key] = value; callback.call(instance,key, old, value); } result.items.push({'key': key, 'value': value , 'found':true}); }else{ result.items.push({'key': key, 'value': value , 'found':false}); } } else { //invalid params throw new Error('args must be a string or object'); } } return result; } /** * Triggers a close event. * * @param {Object} instance The dilog instance. * * @return {undefined} */ function triggerClose(instance) { var found; triggerCallback(instance, function (button) { return found = instance.get('invokeOnCloseOff') !== true && (button.invokeOnClose === true); }); //none of the buttons registered as onclose callback //close the dialog if (!found && instance.isOpen()) { instance.close(); } } /** * Dialogs commands event handler, attached to the dialog commands element. * * @param {Event} event DOM event object. * @param {Object} instance The dilog instance. * * @return {undefined} */ function commandsClickHandler(event, instance) { var target = event.srcElement || event.target; switch (target) { case instance.elements.commands.pin: if (!instance.isPinned()) { pin(instance); } else { unpin(instance); } break; case instance.elements.commands.maximize: if (!instance.isMaximized()) { maximize(instance); } else { restore(instance); } break; case instance.elements.commands.close: triggerClose(instance); break; } return false; } /** * Helper: pins the modeless dialog. * * @param {Object} instance The dialog instance. * * @return {undefined} */ function pin(instance) { //pin the dialog instance.set('pinned', true); } /** * Helper: unpins the modeless dialog. * * @param {Object} instance The dilog instance. * * @return {undefined} */ function unpin(instance) { //unpin the dialog instance.set('pinned', false); } /** * Helper: enlarges the dialog to fill the entire screen. * * @param {Object} instance The dilog instance. * * @return {undefined} */ function maximize(instance) { // allow custom `onmaximize` method dispatchEvent('onmaximize', instance); //maximize the dialog addClass(instance.elements.root, classes.maximized); if (instance.isOpen()) { ensureNoOverflow(); } // allow custom `onmaximized` method dispatchEvent('onmaximized', instance); } /** * Helper: returns the dialog to its former size. * * @param {Object} instance The dilog instance. * * @return {undefined} */ function restore(instance) { // allow custom `onrestore` method dispatchEvent('onrestore', instance); //maximize the dialog removeClass(instance.elements.root, classes.maximized); if (instance.isOpen()) { ensureNoOverflow(); } // allow custom `onrestored` method dispatchEvent('onrestored', instance); } /** * Show or hide the maximize box. * * @param {Object} instance The dilog instance. * @param {Boolean} on True to add the behavior, removes it otherwise. * * @return {undefined} */ function updatePinnable(instance) { if (instance.get('pinnable')) { // add class addClass(instance.elements.root, classes.pinnable); } else { // remove class removeClass(instance.elements.root, classes.pinnable); } } /** * Helper: Fixes the absolutly positioned modal div position. * * @param {Object} instance The dialog instance. * * @return {undefined} */ function addAbsPositionFix(instance) { var scrollLeft = getScrollLeft(); instance.elements.modal.style.marginTop = getScrollTop() + 'px'; instance.elements.modal.style.marginLeft = scrollLeft + 'px'; instance.elements.modal.style.marginRight = (-scrollLeft) + 'px'; } /** * Helper: Removes the absolutly positioned modal div position fix. * * @param {Object} instance The dialog instance. * * @return {undefined} */ function removeAbsPositionFix(instance) { var marginTop = parseInt(instance.elements.modal.style.marginTop, 10); var marginLeft = parseInt(instance.elements.modal.style.marginLeft, 10); instance.elements.modal.style.marginTop = ''; instance.elements.modal.style.marginLeft = ''; instance.elements.modal.style.marginRight = ''; if (instance.isOpen()) { var top = 0, left = 0 ; if (instance.elements.dialog.style.top !== '') { top = parseInt(instance.elements.dialog.style.top, 10); } instance.elements.dialog.style.top = (top + (marginTop - getScrollTop())) + 'px'; if (instance.elements.dialog.style.left !== '') { left = parseInt(instance.elements.dialog.style.left, 10); } instance.elements.dialog.style.left = (left + (marginLeft - getScrollLeft())) + 'px'; } } /** * Helper: Adds/Removes the absolutly positioned modal div position fix based on its pinned setting. * * @param {Object} instance The dialog instance. * * @return {undefined} */ function updateAbsPositionFix(instance) { // if modeless and unpinned add fix if (!instance.get('modal') && !instance.get('pinned')) { addAbsPositionFix(instance); } else { removeAbsPositionFix(instance); } } /** * Toggles the dialog position lock | modeless only. * * @param {Object} instance The dilog instance. * @param {Boolean} on True to make it modal, false otherwise. * * @return {undefined} */ function updatePinned(instance) { if (instance.get('pinned')) { removeClass(instance.elements.root, classes.unpinned); if (instance.isOpen()) { removeAbsPositionFix(instance); } } else { addClass(instance.elements.root, classes.unpinned); if (instance.isOpen() && !instance.isModal()) { addAbsPositionFix(instance); } } } /** * Show or hide the maximize box. * * @param {Object} instance The dilog instance. * @param {Boolean} on True to add the behavior, removes it otherwise. * * @return {undefined} */ function updateMaximizable(instance) { if (instance.get('maximizable')) { // add class addClass(instance.elements.root, classes.maximizable); } else { // remove class removeClass(instance.elements.root, classes.maximizable); } } /** * Show or hide the close box. * * @param {Object} instance The dilog instance. * @param {Boolean} on True to add the behavior, removes it otherwise. * * @return {undefined} */ function updateClosable(instance) { if (instance.get('closable')) { // add class addClass(instance.elements.root, classes.closable); bindClosableEvents(instance); } else { // remove class removeClass(instance.elements.root, classes.closable); unbindClosableEvents(instance); } } var cancelClick = false,// flag to cancel click event if already handled by end resize event (the mousedown, mousemove, mouseup sequence fires a click event.). modalClickHandlerTS=0 // stores last click timestamp to prevent executing the handler twice on double click. ; /** * Helper: closes the modal dialog when clicking the modal * * @param {Event} event DOM event object. * @param {Object} instance The dilog instance. * * @return {undefined} */ function modalClickHandler(event, instance) { if(event.timeStamp - modalClickHandlerTS > 200 && (modalClickHandlerTS = event.timeStamp) && !cancelClick){ var target = event.srcElement || event.target; if (instance.get('closableByDimmer') === true && target === instance.elements.modal) { triggerClose(instance); } } cancelClick = false; } // stores last call timestamp to prevent triggering the callback twice. var callbackTS = 0; // flag to cancel keyup event if already handled by click event (pressing Enter on a focusted button). var cancelKeyup = false; /** * Helper: triggers a button callback * * @param {Object} The dilog instance. * @param {Function} Callback to check which button triggered the event. * * @return {undefined} */ function triggerCallback(instance, check) { if(Date.now() - callbackTS > 200 && (callbackTS = Date.now())){ for (var idx = 0; idx < instance.__internal.buttons.length; idx += 1) { var button = instance.__internal.buttons[idx]; if (!button.element.disabled && check(button)) { var closeEvent = createCloseEvent(idx, button); if (typeof instance.callback === 'function') { instance.callback.apply(instance, [closeEvent]); } //close the dialog only if not canceled. if (closeEvent.cancel === false) { instance.close(); } break; } } } } /** * Clicks event handler, attached to the dialog footer. * * @param {Event} DOM event object. * @param {Object} The dilog instance. * * @return {undefined} */ function buttonsClickHandler(event, instance) { var target = event.srcElement || event.target; triggerCallback(instance, function (button) { // if this button caused the click, cancel keyup event return button.element.contains(target) && (cancelKeyup = true); }); } /** * Keyup event handler, att