|
|
/** * alertifyjs 1.11.4 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications. * Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
( function ( window ) { 'use strict'; /** * Keys enum * @type {Object} */ var keys = { ENTER: 13, ESC: 27, F1: 112, F12: 123, LEFT: 37, RIGHT: 39 }; /** * Default options * @type {Object} */ var defaults = { autoReset:true, basic:false, closable:true, closableByDimmer:true, frameless: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', notifier:{ delay:5, position:'bottom-right', closeButton:false }, 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', } }; //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); } } /** * 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; } }
/** * Use a closure to return proper event listener method. Try to use * `addEventListener` by default but fallback to `attachEvent` for * unsupported browser. The closure simply ensures that the test doesn't * happen every time the method is called. * * @param {Node} el Node element * @param {String} event Event type * @param {Function} fn Callback of event * @return {Function} */ var on = (function () { if (document.addEventListener) { return function (el, event, fn, useCapture) { el.addEventListener(event, fn, useCapture === true); }; } else if (document.attachEvent) { return function (el, event, fn) { el.attachEvent('on' + event, fn); }; } }());
/** * Use a closure to return proper event listener method. Try to use * `removeEventListener` by default but fallback to `detachEvent` for * unsupported browser. The closure simply ensures that the test doesn't * happen every time the method is called. * * @param {Node} el Node element * @param {String} event Event type * @param {Function} fn Callback of event * @return {Function} */ var off = (function () { if (document.removeEventListener) { return function (el, event, fn, useCapture) { el.removeEventListener(event, fn, useCapture === true); }; } else if (document.detachEvent) { return function (el, event, fn) { el.detachEvent('on' + event, fn); }; } }());
/** * 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', };
/** * Helper: initializes the dialog instance * * @return {Number} The total count of currently open modals. */ function initialize(instance){ if(!instance.__internal){
//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, pinned: undefined, movable: undefined, moveBounded:undefined, resizable: undefined, autoReset: undefined, closable: undefined, closableByDimmer: undefined, maximizable: undefined, startMaximized: undefined, pinnable: undefined, transition: 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(); } } //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(typeof oldValue === 'string'){ removeClass(instance.elements.root,classes.prefix + oldValue); } addClass(instance.elements.root, classes.prefix + value); reflow = instance.elements.root.offsetWidth; } /** * 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; }
// 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 = (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; return 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 === target && (cancelKeyup = true); }); }
/** * Keyup event handler, attached to the document.body * * @param {Event} DOM event object. * @param {Object} The dilog instance. * * @return {undefined} */ function keyupHandler(event) { //hitting enter while button has focus will trigger keyup too.
//ignore if handled by clickHandler
if (cancelKeyup) { cancelKeyup = false; return; } var instance = openDialogs[openDialogs.length - 1]; var keyCode = event.keyCode; if (instance.__internal.buttons.length === 0 && keyCode === keys.ESC && instance.get('closable') === true) { triggerClose(instance); return false; }else if (usedKeys.indexOf(keyCode) > -1) { triggerCallback(instance, function (button) { return button.key === keyCode; }); return false; } } /** * Keydown event handler, attached to the document.body * * @param {Event} DOM event object. * @param {Object} The dilog instance. * * @return {undefined} */ function keydownHandler(event) { var instance = openDialogs[openDialogs.length - 1]; var keyCode = event.keyCode; if (keyCode === keys.LEFT || keyCode === keys.RIGHT) { var buttons = instance.__internal.buttons; for (var x = 0; x < buttons.length; x += 1) { if (document.activeElement === buttons[x].element) { switch (keyCode) { case keys.LEFT: buttons[(x || buttons.length) - 1].element.focus(); return; case keys.RIGHT: buttons[(x + 1) % buttons.length].element.focus(); return; } } } }else if (keyCode < keys.F12 + 1 && keyCode > keys.F1 - 1 && usedKeys.indexOf(keyCode) > -1) { event.preventDefault(); event.stopPropagation(); triggerCallback(instance, function (button) { return button.key === keyCode; }); return false; } }
/** * Sets focus to proper dialog element * * @param {Object} instance The dilog instance. * @param {Node} [resetTarget=undefined] DOM element to reset focus to. * * @return {undefined} */ function setFocus(instance, resetTarget) { // reset target has already been determined.
if (resetTarget) { resetTarget.focus(); } else { // current instance focus settings
var focus = instance.__internal.focus; // the focus element.
var element = focus.element;
switch (typeof focus.element) { // a number means a button index
case 'number': if (instance.__internal.buttons.length > focus.element) { //in basic view, skip focusing the buttons.
if (instance.get('basic') === true) { element = instance.elements.reset[0]; } else { element = instance.__internal.buttons[focus.element].element; } } break; // a string means querySelector to select from dialog body contents.
case 'string': element = instance.elements.body.querySelector(focus.element); break; // a function should return the focus element.
case 'function': element = focus.element.call(instance); break; } // if no focus element, default to first reset element.
if ((typeof element === 'undefined' || element === null) && instance.__internal.buttons.length === 0) { element = instance.elements.reset[0]; } // focus
if (element && element.focus) { element.focus(); // if selectable
if (focus.select && element.select) { element.select(); } } } }
/** * Focus event handler, attached to document.body and dialogs own reset links. * handles the focus for modal dialogs only. * * @param {Event} event DOM focus event object. * @param {Object} instance The dilog instance. * * @return {undefined} */ function onReset(event, instance) {
// should work on last modal if triggered from document.body
if (!instance) { for (var x = openDialogs.length - 1; x > -1; x -= 1) { if (openDialogs[x].isModal()) { instance = openDialogs[x]; break; } } } // if modal
if (instance && instance.isModal()) { // determine reset target to enable forward/backward tab cycle.
var resetTarget, target = event.srcElement || event.target; var lastResetElement = target === instance.elements.reset[1] || (instance.__internal.buttons.length === 0 && target === document.body);
// if last reset link, then go to maximize or close
if (lastResetElement) { if (instance.get('maximizable')) { resetTarget = instance.elements.commands.maximize; } else if (instance.get('closable')) { resetTarget = instance.elements.commands.close; } } // if no reset target found, try finding the best button
if (resetTarget === undefined) { if (typeof instance.__internal.focus.element === 'number') { // button focus element, go to first available button
if (target === instance.elements.reset[0]) { resetTarget = instance.elements.buttons.auxiliary.firstChild || instance.elements.buttons.primary.firstChild; } else if (lastResetElement) { //restart the cycle by going to first reset link
resetTarget = instance.elements.reset[0]; } } else { // will reach here when tapping backwards, so go to last child
// The focus element SHOULD NOT be a button (logically!).
if (target === instance.elements.reset[0]) { resetTarget = instance.elements.buttons.primary.lastChild || instance.elements.buttons.auxiliary.lastChild; } } } // focus
setFocus(instance, resetTarget); } } /** * Transition in transitionend event handler. * * @param {Event} TransitionEnd event object. * @param {Object} The dilog instance. * * @return {undefined} */ function handleTransitionInEvent(event, instance) { // clear the timer
clearTimeout(instance.__internal.timerIn);
// once transition is complete, set focus
setFocus(instance);
//restore scroll to prevent document jump
restoreScrollPosition();
// allow handling key up after transition ended.
cancelKeyup = false;
// allow custom `onfocus` method
dispatchEvent('onfocus', instance);
// unbind the event
off(instance.elements.dialog, transition.type, instance.__internal.transitionInHandler);
removeClass(instance.elements.root, classes.animationIn); }
/** * Transition out transitionend event handler. * * @param {Event} TransitionEnd event object. * @param {Object} The dilog instance. * * @return {undefined} */ function handleTransitionOutEvent(event, instance) { // clear the timer
clearTimeout(instance.__internal.timerOut); // unbind the event
off(instance.elements.dialog, transition.type, instance.__internal.transitionOutHandler);
// reset move updates
resetMove(instance); // reset resize updates
resetResize(instance);
// restore if maximized
if (instance.isMaximized() && !instance.get('startMaximized')) { restore(instance); }
// return focus to the last active element
if (alertify.defaults.maintainFocus && instance.__internal.activeElement) { instance.__internal.activeElement.focus(); instance.__internal.activeElement = null; } //destory the instance
if (typeof instance.__internal.destroy === 'function') { instance.__internal.destroy.apply(instance); } } /* Controls moving a dialog around */ //holde the current moving instance
var movable = null, //holds the current X offset when move starts
offsetX = 0, //holds the current Y offset when move starts
offsetY = 0, xProp = 'pageX', yProp = 'pageY', bounds = null, refreshTop = false, moveDelegate = null ;
/** * Helper: sets the element top/left coordinates * * @param {Event} event DOM event object. * @param {Node} element The element being moved. * * @return {undefined} */ function moveElement(event, element) { var left = (event[xProp] - offsetX), top = (event[yProp] - offsetY);
if(refreshTop){ top -= document.body.scrollTop; } element.style.left = left + 'px'; element.style.top = top + 'px'; } /** * Helper: sets the element top/left coordinates within screen bounds * * @param {Event} event DOM event object. * @param {Node} element The element being moved. * * @return {undefined} */ function moveElementBounded(event, element) { var left = (event[xProp] - offsetX), top = (event[yProp] - offsetY);
if(refreshTop){ top -= document.body.scrollTop; } element.style.left = Math.min(bounds.maxLeft, Math.max(bounds.minLeft, left)) + 'px'; if(refreshTop){ element.style.top = Math.min(bounds.maxTop, Math.max(bounds.minTop, top)) + 'px'; }else{ element.style.top = Math.max(bounds.minTop, top) + 'px'; } }
/** * Triggers the start of a move event, attached to the header element mouse down event. * Adds no-selection class to the body, disabling selection while moving. * * @param {Event} event DOM event object. * @param {Object} instance The dilog instance. * * @return {Boolean} false */ function beginMove(event, instance) { if (resizable === null && !instance.isMaximized() && instance.get('movable')) { var eventSrc, left=0, top=0; if (event.type === 'touchstart') { event.preventDefault(); eventSrc = event.targetTouches[0]; xProp = 'clientX'; yProp = 'clientY'; } else if (event.button === 0) { eventSrc = event; }
if (eventSrc) {
var element = instance.elements.dialog; addClass(element, classes.capture);
if (element.style.left) { left = parseInt(element.style.left, 10); }
if (element.style.top) { top = parseInt(element.style.top, 10); } offsetX = eventSrc[xProp] - left; offsetY = eventSrc[yProp] - top;
if(instance.isModal()){ offsetY += instance.elements.modal.scrollTop; }else if(instance.isPinned()){ offsetY -= document.body.scrollTop; } if(instance.get('moveBounded')){ var current = element, offsetLeft = -left, offsetTop = -top; //calc offset
do { offsetLeft += current.offsetLeft; offsetTop += current.offsetTop; } while (current = current.offsetParent); bounds = { maxLeft : offsetLeft, minLeft : -offsetLeft, maxTop : document.documentElement.clientHeight - element.clientHeight - offsetTop, minTop : -offsetTop }; moveDelegate = moveElementBounded; }else{ bounds = null; moveDelegate = moveElement; } // allow custom `onmove` method
dispatchEvent('onmove', instance);
refreshTop = !instance.isModal() && instance.isPinned(); movable = instance; moveDelegate(eventSrc, element); addClass(document.body, classes.noSelection); return false; } } }
/** * The actual move handler, attached to document.body mousemove event. * * @param {Event} event DOM event object. * * @return {undefined} */ function move(event) { if (movable) { var eventSrc; if (event.type === 'touchmove') { event.preventDefault(); eventSrc = event.targetTouches[0]; } else if (event.button === 0) { eventSrc = event; } if (eventSrc) { moveDelegate(eventSrc, movable.elements.dialog); } } }
/** * Triggers the end of a move event, attached to document.body mouseup event. * Removes no-selection class from document.body, allowing selection. * * @return {undefined} */ function endMove() { if (movable) { var instance = movable; movable = bounds = null; removeClass(document.body, classes.noSelection); removeClass(instance.elements.dialog, classes.capture); // allow custom `onmoved` method
dispatchEvent('onmoved', instance); } }
/** * Resets any changes made by moving the element to its original state, * * @param {Object} instance The dilog instance. * * @return {undefined} */ function resetMove(instance) { movable = null; var element = instance.elements.dialog; element.style.left = element.style.top = ''; }
/** * Updates the dialog move behavior. * * @param {Object} instance The dilog instance. * @param {Boolean} on True to add the behavior, removes it otherwise. * * @return {undefined} */ function updateMovable(instance) { if (instance.get('movable')) { // add class
addClass(instance.elements.root, classes.movable); if (instance.isOpen()) { bindMovableEvents(instance); } } else {
//reset
resetMove(instance); // remove class
removeClass(instance.elements.root, classes.movable); if (instance.isOpen()) { unbindMovableEvents(instance); } } }
/* Controls moving a dialog around */ //holde the current instance being resized
var resizable = null, //holds the staring left offset when resize starts.
startingLeft = Number.Nan, //holds the staring width when resize starts.
startingWidth = 0, //holds the initial width when resized for the first time.
minWidth = 0, //holds the offset of the resize handle.
handleOffset = 0 ;
/** * Helper: sets the element width/height and updates left coordinate if neccessary. * * @param {Event} event DOM mousemove event object. * @param {Node} element The element being moved. * @param {Boolean} pinned A flag indicating if the element being resized is pinned to the screen. * * @return {undefined} */ function resizeElement(event, element, pageRelative) {
//calculate offsets from 0,0
var current = element; var offsetLeft = 0; var offsetTop = 0; do { offsetLeft += current.offsetLeft; offsetTop += current.offsetTop; } while (current = current.offsetParent);
// determine X,Y coordinates.
var X, Y; if (pageRelative === true) { X = event.pageX; Y = event.pageY; } else { X = event.clientX; Y = event.clientY; } // rtl handling
var isRTL = isRightToLeft(); if (isRTL) { // reverse X
X = document.body.offsetWidth - X; // if has a starting left, calculate offsetRight
if (!isNaN(startingLeft)) { offsetLeft = document.body.offsetWidth - offsetLeft - element.offsetWidth; } }
// set width/height
element.style.height = (Y - offsetTop + handleOffset) + 'px'; element.style.width = (X - offsetLeft + handleOffset) + 'px';
// if the element being resized has a starting left, maintain it.
// the dialog is centered, divide by half the offset to maintain the margins.
if (!isNaN(startingLeft)) { var diff = Math.abs(element.offsetWidth - startingWidth) * 0.5; if (isRTL) { //negate the diff, why?
//when growing it should decrease left
//when shrinking it should increase left
diff *= -1; } if (element.offsetWidth > startingWidth) { //growing
element.style.left = (startingLeft + diff) + 'px'; } else if (element.offsetWidth >= minWidth) { //shrinking
element.style.left = (startingLeft - diff) + 'px'; } } }
/** * Triggers the start of a resize event, attached to the resize handle element mouse down event. * Adds no-selection class to the body, disabling selection while moving. * * @param {Event} event DOM event object. * @param {Object} instance The dilog instance. * * @return {Boolean} false */ function beginResize(event, instance) { if (!instance.isMaximized()) { var eventSrc; if (event.type === 'touchstart') { event.preventDefault(); eventSrc = event.targetTouches[0]; } else if (event.button === 0) { eventSrc = event; } if (eventSrc) { // allow custom `onresize` method
dispatchEvent('onresize', instance); resizable = instance; handleOffset = instance.elements.resizeHandle.offsetHeight / 2; var element = instance.elements.dialog; addClass(element, classes.capture); startingLeft = parseInt(element.style.left, 10); element.style.height = element.offsetHeight + 'px'; element.style.minHeight = instance.elements.header.offsetHeight + instance.elements.footer.offsetHeight + 'px'; element.style.width = (startingWidth = element.offsetWidth) + 'px';
if (element.style.maxWidth !== 'none') { element.style.minWidth = (minWidth = element.offsetWidth) + 'px'; } element.style.maxWidth = 'none'; addClass(document.body, classes.noSelection); return false; } } }
/** * The actual resize handler, attached to document.body mousemove event. * * @param {Event} event DOM event object. * * @return {undefined} */ function resize(event) { if (resizable) { var eventSrc; if (event.type === 'touchmove') { event.preventDefault(); eventSrc = event.targetTouches[0]; } else if (event.button === 0) { eventSrc = event; } if (eventSrc) { resizeElement(eventSrc, resizable.elements.dialog, !resizable.get('modal') && !resizable.get('pinned')); } } }
/** * Triggers the end of a resize event, attached to document.body mouseup event. * Removes no-selection class from document.body, allowing selection. * * @return {undefined} */ function endResize() { if (resizable) { var instance = resizable; resizable = null; removeClass(document.body, classes.noSelection); removeClass(instance.elements.dialog, classes.capture); cancelClick = true; // allow custom `onresized` method
dispatchEvent('onresized', instance); } }
/** * Resets any changes made by resizing the element to its original state. * * @param {Object} instance The dilog instance. * * @return {undefined} */ function resetResize(instance) { resizable = null; var element = instance.elements.dialog; if (element.style.maxWidth === 'none') { //clear inline styles.
element.style.maxWidth = element.style.minWidth = element.style.width = element.style.height = element.style.minHeight = element.style.left = ''; //reset variables.
startingLeft = Number.Nan; startingWidth = minWidth = handleOffset = 0; } }
/** * Updates the dialog move behavior. * * @param {Object} instance The dilog instance. * @param {Boolean} on True to add the behavior, removes it otherwise. * * @return {undefined} */ function updateResizable(instance) { if (instance.get('resizable')) { // add class
addClass(instance.elements.root, classes.resizable); if (instance.isOpen()) { bindResizableEvents(instance); } } else { //reset
resetResize(instance); // remove class
removeClass(instance.elements.root, classes.resizable); if (instance.isOpen()) { unbindResizableEvents(instance); } } }
/** * Reset move/resize on window resize. * * @param {Event} event window resize event object. * * @return {undefined} */ function windowResize(/*event*/) { for (var x = 0; x < openDialogs.length; x += 1) { var instance = openDialogs[x]; if (instance.get('autoReset')) { resetMove(instance); resetResize(instance); } } } /** * Bind dialogs events * * @param {Object} instance The dilog instance. * * @return {undefined} */ function bindEvents(instance) { // if first dialog, hook global handlers
if (openDialogs.length === 1) { //global
on(window, 'resize', windowResize); on(document.body, 'keyup', keyupHandler); on(document.body, 'keydown', keydownHandler); on(document.body, 'focus', onReset);
//move
on(document.documentElement, 'mousemove', move); on(document.documentElement, 'touchmove', move); on(document.documentElement, 'mouseup', endMove); on(document.documentElement, 'touchend', endMove); //resize
on(document.documentElement, 'mousemove', resize); on(document.documentElement, 'touchmove', resize); on(document.documentElement, 'mouseup', endResize); on(document.documentElement, 'touchend', endResize); }
// common events
on(instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler); on(instance.elements.footer, 'click', instance.__internal.buttonsClickHandler); on(instance.elements.reset[0], 'focus', instance.__internal.resetHandler); on(instance.elements.reset[1], 'focus', instance.__internal.resetHandler);
//prevent handling key up when dialog is being opened by a key stroke.
cancelKeyup = true; // hook in transition handler
on(instance.elements.dialog, transition.type, instance.__internal.transitionInHandler);
// modelss only events
if (!instance.get('modal')) { bindModelessEvents(instance); }
// resizable
if (instance.get('resizable')) { bindResizableEvents(instance); }
// movable
if (instance.get('movable')) { bindMovableEvents(instance); } }
/** * Unbind dialogs events * * @param {Object} instance The dilog instance. * * @return {undefined} */ function unbindEvents(instance) { // if last dialog, remove global handlers
if (openDialogs.length === 1) { //global
off(window, 'resize', windowResize); off(document.body, 'keyup', keyupHandler); off(document.body, 'keydown', keydownHandler); off(document.body, 'focus', onReset); //move
off(document.documentElement, 'mousemove', move); off(document.documentElement, 'mouseup', endMove); //resize
off(document.documentElement, 'mousemove', resize); off(document.documentElement, 'mouseup', endResize); }
// common events
off(instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler); off(instance.elements.footer, 'click', instance.__internal.buttonsClickHandler); off(instance.elements.reset[0], 'focus', instance.__internal.resetHandler); off(instance.elements.reset[1], 'focus', instance.__internal.resetHandler);
// hook out transition handler
on(instance.elements.dialog, transition.type, instance.__internal.transitionOutHandler);
// modelss only events
if (!instance.get('modal')) { unbindModelessEvents(instance); }
// movable
if (instance.get('movable')) { unbindMovableEvents(instance); }
// resizable
if (instance.get('resizable')) { unbindResizableEvents(instance); }
}
/** * Bind modeless specific events * * @param {Object} instance The dilog instance. * * @return {undefined} */ function bindModelessEvents(instance) { on(instance.elements.dialog, 'focus', instance.__internal.bringToFrontHandler, true); }
/** * Unbind modeless specific events * * @param {Object} instance The dilog instance. * * @return {undefined} */ function unbindModelessEvents(instance) { off(instance.elements.dialog, 'focus', instance.__internal.bringToFrontHandler, true); }
/** * Bind movable specific events * * @param {Object} instance The dilog instance. * * @return {undefined} */ function bindMovableEvents(instance) { on(instance.elements.header, 'mousedown', instance.__internal.beginMoveHandler); on(instance.elements.header, 'touchstart', instance.__internal.beginMoveHandler); }
/** * Unbind movable specific events * * @param {Object} instance The dilog instance. * * @return {undefined} */ function unbindMovableEvents(instance) { off(instance.elements.header, 'mousedown', instance.__internal.beginMoveHandler); off(instance.elements.header, 'touchstart', instance.__internal.beginMoveHandler); }
/** * Bind resizable specific events * * @param {Object} instance The dilog instance. * * @return {undefined} */ function bindResizableEvents(instance) { on(instance.elements.resizeHandle, 'mousedown', instance.__internal.beginResizeHandler); on(instance.elements.resizeHandle, 'touchstart', instance.__internal.beginResizeHandler); }
/** * Unbind resizable specific events * * @param {Object} instance The dilog instance. * * @return {undefined} */ function unbindResizableEvents(instance) { off(instance.elements.resizeHandle, 'mousedown', instance.__internal.beginResizeHandler); off(instance.elements.resizeHandle, 'touchstart', instance.__internal.beginResizeHandler); }
/** * Bind closable events * * @param {Object} instance The dilog instance. * * @return {undefined} */ function bindClosableEvents(instance) { on(instance.elements.modal, 'click', instance.__internal.modalClickHandler); }
/** * Unbind closable specific events * * @param {Object} instance The dilog instance. * * @return {undefined} */ function unbindClosableEvents(instance) { off(instance.elements.modal, 'click', instance.__internal.modalClickHandler); } // dialog API
return { __init:initialize, /** * Check if dialog is currently open * * @return {Boolean} */ isOpen: function () { return this.__internal.isOpen; }, isModal: function (){ return this.elements.root.className.indexOf(classes.modeless) < 0; }, isMaximized:function(){ return this.elements.root.className.indexOf(classes.maximized) > -1; }, isPinned:function(){ return this.elements.root.className.indexOf(classes.unpinned) < 0; }, maximize:function(){ if(!this.isMaximized()){ maximize(this); } return this; }, restore:function(){ if(this.isMaximized()){ restore(this); } return this; }, pin:function(){ if(!this.isPinned()){ pin(this); } return this; }, unpin:function(){ if(this.isPinned()){ unpin(this); } return this; }, bringToFront:function(){ bringToFront(null, this); return this; }, /** * Move the dialog to a specific x/y coordinates * * @param {Number} x The new dialog x coordinate in pixels. * @param {Number} y The new dialog y coordinate in pixels. * * @return {Object} The dialog instance. */ moveTo:function(x,y){ if(!isNaN(x) && !isNaN(y)){ // allow custom `onmove` method
dispatchEvent('onmove', this); var element = this.elements.dialog, current = element, offsetLeft = 0, offsetTop = 0; //subtract existing left,top
if (element.style.left) { offsetLeft -= parseInt(element.style.left, 10); } if (element.style.top) { offsetTop -= parseInt(element.style.top, 10); } //calc offset
do { offsetLeft += current.offsetLeft; offsetTop += current.offsetTop; } while (current = current.offsetParent);
//calc left, top
var left = (x - offsetLeft); var top = (y - offsetTop);
//// rtl handling
if (isRightToLeft()) { left *= -1; }
element.style.left = left + 'px'; element.style.top = top + 'px'; // allow custom `onmoved` method
dispatchEvent('onmoved', this); } return this; }, /** * Resize the dialog to a specific width/height (the dialog must be 'resizable'). * The dialog can be resized to: * A minimum width equal to the initial display width * A minimum height equal to the sum of header/footer heights. * * * @param {Number or String} width The new dialog width in pixels or in percent. * @param {Number or String} height The new dialog height in pixels or in percent. * * @return {Object} The dialog instance. */ resizeTo:function(width,height){ var w = parseFloat(width), h = parseFloat(height), regex = /(\d*\.\d+|\d+)%/ ;
if(!isNaN(w) && !isNaN(h) && this.get('resizable') === true){ // allow custom `onresize` method
dispatchEvent('onresize', this); if(('' + width).match(regex)){ w = w / 100 * document.documentElement.clientWidth ; }
if(('' + height).match(regex)){ h = h / 100 * document.documentElement.clientHeight; }
var element = this.elements.dialog; if (element.style.maxWidth !== 'none') { element.style.minWidth = (minWidth = element.offsetWidth) + 'px'; } element.style.maxWidth = 'none'; element.style.minHeight = this.elements.header.offsetHeight + this.elements.footer.offsetHeight + 'px'; element.style.width = w + 'px'; element.style.height = h + 'px'; // allow custom `onresized` method
dispatchEvent('onresized', this); } return this; }, /** * Gets or Sets dialog settings/options * * @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). * * @return {undefined} */ setting : function (key, value) { var self = this; var result = update(this, this.__internal.options, function(k,o,n){ optionUpdated(self,k,o,n); }, key, value); if(result.op === 'get'){ if(result.found){ return result.value; }else if(typeof this.settings !== 'undefined'){ return update(this, this.settings, this.settingUpdated || function(){}, key, value).value; }else{ return undefined; } }else if(result.op === 'set'){ if(result.items.length > 0){ var callback = this.settingUpdated || function(){}; for(var x=0;x<result.items.length;x+=1){ var item = result.items[x]; if(!item.found && typeof this.settings !== 'undefined'){ update(this, this.settings, callback, item.key, item.value); } } } return this; } }, /** * [Alias] Sets dialog settings/options */ set:function(key, value){ this.setting(key,value); return this; }, /** * [Alias] Gets dialog settings/options */ get:function(key){ return this.setting(key); }, /** * Sets dialog header * @content {string or element} * * @return {undefined} */ setHeader:function(content){ if(typeof content === 'string'){ clearContents(this.elements.header); this.elements.header.innerHTML = content; }else if (content instanceof window.HTMLElement && this.elements.header.firstChild !== content){ clearContents(this.elements.header); this.elements.header.appendChild(content); } return this; }, /** * Sets dialog contents * @content {string or element} * * @return {undefined} */ setContent:function(content){ if(typeof content === 'string'){ clearContents(this.elements.content); this.elements.content.innerHTML = content; }else if (content instanceof window.HTMLElement && this.elements.content.firstChild !== content){ clearContents(this.elements.content); this.elements.content.appendChild(content); } return this; }, /** * Show the dialog as modal * * @return {Object} the dialog instance. */ showModal: function(className){ return this.show(true, className); }, /** * Show the dialog * * @return {Object} the dialog instance. */ show: function (modal, className) { // ensure initialization
initialize(this);
if ( !this.__internal.isOpen ) {
// add to open dialogs
this.__internal.isOpen = true; openDialogs.push(this);
// save last focused element
if(alertify.defaults.maintainFocus){ this.__internal.activeElement = document.activeElement; }
// set tabindex attribute on body element this allows script to give it focusable
if(!document.body.hasAttribute('tabindex')) { document.body.setAttribute( 'tabindex', tabindex = '0'); }
//allow custom dom manipulation updates before showing the dialog.
if(typeof this.prepare === 'function'){ this.prepare(); }
bindEvents(this);
if(modal !== undefined){ this.set('modal', modal); }
//save scroll to prevent document jump
saveScrollPosition();
ensureNoOverflow();
// allow custom dialog class on show
if(typeof className === 'string' && className !== ''){ this.__internal.className = className; addClass(this.elements.root, className); }
// maximize if start maximized
if ( this.get('startMaximized')) { this.maximize(); }else if(this.isMaximized()){ restore(this); }
updateAbsPositionFix(this); this.elements.root.removeAttribute('style'); removeClass(this.elements.root, classes.animationOut); addClass(this.elements.root, classes.animationIn);
// set 1s fallback in case transition event doesn't fire
clearTimeout( this.__internal.timerIn); this.__internal.timerIn = setTimeout( this.__internal.transitionInHandler, transition.supported ? 1000 : 100 );
if(isSafari){ // force desktop safari reflow
var root = this.elements.root; root.style.display = 'none'; setTimeout(function(){root.style.display = 'block';}, 0); }
//reflow
reflow = this.elements.root.offsetWidth; // show dialog
removeClass(this.elements.root, classes.hidden);
// internal on show event
if(typeof this.hooks.onshow === 'function'){ this.hooks.onshow.call(this); }
// allow custom `onshow` method
dispatchEvent('onshow', this);
}else{ // reset move updates
resetMove(this); // reset resize updates
resetResize(this); // shake the dialog to indicate its already open
addClass(this.elements.dialog, classes.shake); var self = this; setTimeout(function(){ removeClass(self.elements.dialog, classes.shake); },200); } return this; }, /** * Close the dialog * * @return {Object} The dialog instance */ close: function () { if (this.__internal.isOpen ) { // custom `onclosing` event
if(dispatchEvent('onclosing', this) !== false){
unbindEvents(this);
removeClass(this.elements.root, classes.animationIn); addClass(this.elements.root, classes.animationOut);
// set 1s fallback in case transition event doesn't fire
clearTimeout( this.__internal.timerOut ); this.__internal.timerOut = setTimeout( this.__internal.transitionOutHandler, transition.supported ? 1000 : 100 ); // hide dialog
addClass(this.elements.root, classes.hidden); //reflow
reflow = this.elements.modal.offsetWidth;
// remove custom dialog class on hide
if (typeof this.__internal.className !== 'undefined' && this.__internal.className !== '') { removeClass(this.elements.root, this.__internal.className); }
// internal on close event
if(typeof this.hooks.onclose === 'function'){ this.hooks.onclose.call(this); }
// allow custom `onclose` method
dispatchEvent('onclose', this);
//remove from open dialogs
openDialogs.splice(openDialogs.indexOf(this),1); this.__internal.isOpen = false;
ensureNoOverflow(); }
} // last dialog and tab index was set by us, remove it.
if(!openDialogs.length && tabindex === '0'){ document.body.removeAttribute('tabindex'); } return this; }, /** * Close all open dialogs except this. * * @return {undefined} */ closeOthers:function(){ alertify.closeAll(this); return this; }, /** * Destroys this dialog instance * * @return {undefined} */ destroy:function(){ if(this.__internal) { if (this.__internal.isOpen ) { //mark dialog for destruction, this will be called on tranistionOut event.
this.__internal.destroy = function(){ destruct(this, initialize); }; //close the dialog to unbind all events.
this.close(); }else if(!this.__internal.destroy){ destruct(this, initialize); } } return this; }, }; } () ); var notifier = (function () { var reflow, element, openInstances = [], classes = { base: 'alertify-notifier', 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' }; /** * Helper: initializes the notifier instance * */ function initialize(instance) {
if (!instance.__internal) { instance.__internal = { position: alertify.defaults.notifier.position, delay: alertify.defaults.notifier.delay, };
element = document.createElement('DIV');
updatePosition(instance); }
//add to DOM tree.
if (element.parentNode !== document.body) { document.body.appendChild(element); } }
function pushInstance(instance) { instance.__internal.pushed = true; openInstances.push(instance); } function popInstance(instance) { openInstances.splice(openInstances.indexOf(instance), 1); instance.__internal.pushed = false; } /** * Helper: update the notifier instance position * */ function updatePosition(instance) { element.className = classes.base; switch (instance.__internal.position) { case 'top-right': addClass(element, classes.top + ' ' + classes.right); break; case 'top-left': addClass(element, classes.top + ' ' + classes.left); break; case 'top-center': addClass(element, classes.top + ' ' + classes.center); break; case 'bottom-left': addClass(element, classes.bottom + ' ' + classes.left); break; case 'bottom-center': addClass(element, classes.bottom + ' ' + classes.center); break;
default: case 'bottom-right': addClass(element, classes.bottom + ' ' + classes.right); break; } }
/** * creates a new notification message * * @param {DOMElement} message The notifier message element * @param {Number} wait Time (in ms) to wait before the message is dismissed, a value of 0 means keep open till clicked. * @param {Function} callback A callback function to be invoked when the message is dismissed. * * @return {undefined} */ function create(div, callback) {
function clickDelegate(event, instance) { if(!instance.__internal.closeButton || event.target.getAttribute('data-close') === 'true'){ instance.dismiss(true); } }
function transitionDone(event, instance) { // unbind event
off(instance.element, transition.type, transitionDone); // remove the message
element.removeChild(instance.element); }
function initialize(instance) { if (!instance.__internal) { instance.__internal = { pushed: false, delay : undefined, timer: undefined, clickHandler: undefined, transitionEndHandler: undefined, transitionTimeout: undefined }; instance.__internal.clickHandler = delegate(instance, clickDelegate); instance.__internal.transitionEndHandler = delegate(instance, transitionDone); } return instance; } function clearTimers(instance) { clearTimeout(instance.__internal.timer); clearTimeout(instance.__internal.transitionTimeout); } return initialize({ /* notification DOM element*/ element: div, /* * Pushes a notification message * @param {string or DOMElement} content The notification message content * @param {Number} wait The time (in seconds) to wait before the message is dismissed, a value of 0 means keep open till clicked. * */ push: function (_content, _wait) { if (!this.__internal.pushed) {
pushInstance(this); clearTimers(this);
var content, wait; switch (arguments.length) { case 0: wait = this.__internal.delay; break; case 1: if (typeof (_content) === 'number') { wait = _content; } else { content = _content; wait = this.__internal.delay; } break; case 2: content = _content; wait = _wait; break; } this.__internal.closeButton = alertify.defaults.notifier.closeButton; // set contents
if (typeof content !== 'undefined') { this.setContent(content); } // append or insert
if (notifier.__internal.position.indexOf('top') < 0) { element.appendChild(this.element); } else { element.insertBefore(this.element, element.firstChild); } reflow = this.element.offsetWidth; addClass(this.element, classes.visible); // attach click event
on(this.element, 'click', this.__internal.clickHandler); return this.delay(wait); } return this; }, /* * {Function} callback function to be invoked before dismissing the notification message. * Remarks: A return value === 'false' will cancel the dismissal * */ ondismiss: function () { }, /* * {Function} callback function to be invoked when the message is dismissed. * */ callback: callback, /* * Dismisses the notification message * @param {Boolean} clicked A flag indicating if the dismissal was caused by a click. * */ dismiss: function (clicked) { if (this.__internal.pushed) { clearTimers(this); if (!(typeof this.ondismiss === 'function' && this.ondismiss.call(this) === false)) { //detach click event
off(this.element, 'click', this.__internal.clickHandler); // ensure element exists
if (typeof this.element !== 'undefined' && this.element.parentNode === element) { //transition end or fallback
this.__internal.transitionTimeout = setTimeout(this.__internal.transitionEndHandler, transition.supported ? 1000 : 100); removeClass(this.element, classes.visible);
// custom callback on dismiss
if (typeof this.callback === 'function') { this.callback.call(this, clicked); } } popInstance(this); } } return this; }, /* * Delays the notification message dismissal * @param {Number} wait The time (in seconds) to wait before the message is dismissed, a value of 0 means keep open till clicked. * */ delay: function (wait) { clearTimers(this); this.__internal.delay = typeof wait !== 'undefined' && !isNaN(+wait) ? +wait : notifier.__internal.delay; if (this.__internal.delay > 0) { var self = this; this.__internal.timer = setTimeout(function () { self.dismiss(); }, this.__internal.delay * 1000); } return this; }, /* * Sets the notification message contents * @param {string or DOMElement} content The notification message content * */ setContent: function (content) { if (typeof content === 'string') { clearContents(this.element); this.element.innerHTML = content; } else if (content instanceof window.HTMLElement && this.element.firstChild !== content) { clearContents(this.element); this.element.appendChild(content); } if(this.__internal.closeButton){ var close = document.createElement('span'); addClass(close, classes.close); close.setAttribute('data-close', true); this.element.appendChild(close); } return this; }, /* * Dismisses all open notifications except this. * */ dismissOthers: function () { notifier.dismissAll(this); return this; } }); }
//notifier api
return { /** * Gets or Sets notifier settings. * * @param {string} key The setting name * @param {Variant} value The setting value. * * @return {Object} if the called as a setter, return the notifier instance. */ setting: function (key, value) { //ensure init
initialize(this);
if (typeof value === 'undefined') { //get
return this.__internal[key]; } else { //set
switch (key) { case 'position': this.__internal.position = value; updatePosition(this); break; case 'delay': this.__internal.delay = value; break; } } return this; }, /** * [Alias] Sets dialog settings/options */ set:function(key,value){ this.setting(key,value); return this; }, /** * [Alias] Gets dialog settings/options */ get:function(key){ return this.setting(key); }, /** * Creates a new notification message * * @param {string} type The type of notification message (simply a CSS class name 'ajs-{type}' to be added). * @param {Function} callback A callback function to be invoked when the message is dismissed. * * @return {undefined} */ create: function (type, callback) { //ensure notifier init
initialize(this); //create new notification message
var div = document.createElement('div'); div.className = classes.message + ((typeof type === 'string' && type !== '') ? ' ajs-' + type : ''); return create(div, callback); }, /** * Dismisses all open notifications. * * @param {Object} excpet [optional] The notification object to exclude from dismissal. * */ dismissAll: function (except) { var clone = openInstances.slice(0); for (var x = 0; x < clone.length; x += 1) { var instance = clone[x]; if (except === undefined || except !== instance) { instance.dismiss(); } } } }; })();
/** * Alertify public API * This contains everything that is exposed through the alertify object. * * @return {Object} */ function Alertify() {
// holds a references of created dialogs
var dialogs = {};
/** * 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 extend(sub, base) { // copy dialog pototype over definition.
for (var prop in base) { if (base.hasOwnProperty(prop)) { sub[prop] = base[prop]; } } return sub; }
/** * Helper: returns a dialog instance from saved dialogs. * and initializes the dialog if its not already initialized. * * @name {String} name The dialog name. * * @return {Object} The dialog instance. */ function get_dialog(name) { var dialog = dialogs[name].dialog; //initialize the dialog if its not already initialized.
if (dialog && typeof dialog.__init === 'function') { dialog.__init(dialog); } return dialog; }
/** * Helper: registers a new dialog definition. * * @name {String} name The dialog name. * @Factory {Function} Factory a function resposible for creating dialog prototype. * @transient {Boolean} transient True to create a new dialog instance each time the dialog is invoked, false otherwise. * @base {String} base the name of another dialog to inherit from. * * @return {Object} The dialog definition. */ function register(name, Factory, transient, base) { var definition = { dialog: null, factory: Factory };
//if this is based on an existing dialog, create a new definition
//by applying the new protoype over the existing one.
if (base !== undefined) { definition.factory = function () { return extend(new dialogs[base].factory(), new Factory()); }; }
if (!transient) { //create a new definition based on dialog
definition.dialog = extend(new definition.factory(), dialog); } return dialogs[name] = definition; }
return { /** * Alertify defaults * * @type {Object} */ defaults: defaults, /** * Dialogs factory * * @param {string} Dialog name. * @param {Function} A Dialog factory function. * @param {Boolean} Indicates whether to create a singleton or transient dialog. * @param {String} The name of the base type to inherit from. */ dialog: function (name, Factory, transient, base) {
// get request, create a new instance and return it.
if (typeof Factory !== 'function') { return get_dialog(name); }
if (this.hasOwnProperty(name)) { throw new Error('alertify.dialog: name already exists'); }
// register the dialog
var definition = register(name, Factory, transient, base);
if (transient) {
// make it public
this[name] = function () { //if passed with no params, consider it a get request
if (arguments.length === 0) { return definition.dialog; } else { var instance = extend(new definition.factory(), dialog); //ensure init
if (instance && typeof instance.__init === 'function') { instance.__init(instance); } instance['main'].apply(instance, arguments); return instance['show'].apply(instance); } }; } else { // make it public
this[name] = function () { //ensure init
if (definition.dialog && typeof definition.dialog.__init === 'function') { definition.dialog.__init(definition.dialog); } //if passed with no params, consider it a get request
if (arguments.length === 0) { return definition.dialog; } else { var dialog = definition.dialog; dialog['main'].apply(definition.dialog, arguments); return dialog['show'].apply(definition.dialog); } }; } }, /** * Close all open dialogs. * * @param {Object} excpet [optional] The dialog object to exclude from closing. * * @return {undefined} */ closeAll: function (except) { var clone = openDialogs.slice(0); for (var x = 0; x < clone.length; x += 1) { var instance = clone[x]; if (except === undefined || except !== instance) { instance.close(); } } }, /** * Gets or Sets dialog settings/options. if the dialog is transient, this call does nothing. * * @param {string} name The dialog name. * @param {String|Object} key A string specifying a propery name or a collection of key/value pairs. * @param {Variant} value Optional, the value associated with the key (in case it was a string). * * @return {undefined} */ setting: function (name, key, value) {
if (name === 'notifier') { return notifier.setting(key, value); }
var dialog = get_dialog(name); if (dialog) { return dialog.setting(key, value); } }, /** * [Alias] Sets dialog settings/options */ set: function(name,key,value){ return this.setting(name, key,value); }, /** * [Alias] Gets dialog settings/options */ get: function(name, key){ return this.setting(name, key); }, /** * Creates a new notification message. * If a type is passed, a class name "ajs-{type}" will be added. * This allows for custom look and feel for various types of notifications. * * @param {String | DOMElement} [message=undefined] Message text * @param {String} [type=''] Type of log message * @param {String} [wait=''] Time (in seconds) to wait before auto-close * @param {Function} [callback=undefined] A callback function to be invoked when the log is closed. * * @return {Object} Notification object. */ notify: function (message, type, wait, callback) { return notifier.create(type, callback).push(message, wait); }, /** * Creates a new notification message. * * @param {String} [message=undefined] Message text * @param {String} [wait=''] Time (in seconds) to wait before auto-close * @param {Function} [callback=undefined] A callback function to be invoked when the log is closed. * * @return {Object} Notification object. */ message: function (message, wait, callback) { return notifier.create(null, callback).push(message, wait); }, /** * Creates a new notification message of type 'success'. * * @param {String} [message=undefined] Message text * @param {String} [wait=''] Time (in seconds) to wait before auto-close * @param {Function} [callback=undefined] A callback function to be invoked when the log is closed. * * @return {Object} Notification object. */ success: function (message, wait, callback) { return notifier.create('success', callback).push(message, wait); }, /** * Creates a new notification message of type 'error'. * * @param {String} [message=undefined] Message text * @param {String} [wait=''] Time (in seconds) to wait before auto-close * @param {Function} [callback=undefined] A callback function to be invoked when the log is closed. * * @return {Object} Notification object. */ error: function (message, wait, callback) { return notifier.create('error', callback).push(message, wait); }, /** * Creates a new notification message of type 'warning'. * * @param {String} [message=undefined] Message text * @param {String} [wait=''] Time (in seconds) to wait before auto-close * @param {Function} [callback=undefined] A callback function to be invoked when the log is closed. * * @return {Object} Notification object. */ warning: function (message, wait, callback) { return notifier.create('warning', callback).push(message, wait); }, /** * Dismisses all open notifications * * @return {undefined} */ dismissAll: function () { notifier.dismissAll(); } }; } var alertify = new Alertify();
/** * Alert dialog definition * * invoked by: * alertify.alert(message); * alertify.alert(title, message); * alertify.alert(message, onok); * alertify.alert(title, message, onok); */ alertify.dialog('alert', function () { return { main: function (_title, _message, _onok) { var title, message, onok; switch (arguments.length) { case 1: message = _title; break; case 2: if (typeof _message === 'function') { message = _title; onok = _message; } else { title = _title; message = _message; } break; case 3: title = _title; message = _message; onok = _onok; break; } this.set('title', title); this.set('message', message); this.set('onok', onok); return this; }, setup: function () { return { buttons: [ { text: alertify.defaults.glossary.ok, key: keys.ESC, invokeOnClose: true, className: alertify.defaults.theme.ok, } ], focus: { element: 0, select: false }, options: { maximizable: false, resizable: false } }; }, build: function () { // nothing
}, prepare: function () { //nothing
}, setMessage: function (message) { this.setContent(message); }, settings: { message: undefined, onok: undefined, label: undefined, }, settingUpdated: function (key, oldValue, newValue) { switch (key) { case 'message': this.setMessage(newValue); break; case 'label': if (this.__internal.buttons[0].element) { this.__internal.buttons[0].element.innerHTML = newValue; } break; } }, callback: function (closeEvent) { if (typeof this.get('onok') === 'function') { var returnValue = this.get('onok').call(this, closeEvent); if (typeof returnValue !== 'undefined') { closeEvent.cancel = !returnValue; } } } }; }); /** * Confirm dialog object * * alertify.confirm(message); * alertify.confirm(message, onok); * alertify.confirm(message, onok, oncancel); * alertify.confirm(title, message, onok, oncancel); */ alertify.dialog('confirm', function () {
var autoConfirm = { timer: null, index: null, text: null, duration: null, task: function (event, self) { if (self.isOpen()) { self.__internal.buttons[autoConfirm.index].element.innerHTML = autoConfirm.text + ' (‏' + autoConfirm.duration + '‏) '; autoConfirm.duration -= 1; if (autoConfirm.duration === -1) { clearAutoConfirm(self); var button = self.__internal.buttons[autoConfirm.index]; var closeEvent = createCloseEvent(autoConfirm.index, button);
if (typeof self.callback === 'function') { self.callback.apply(self, [closeEvent]); } //close the dialog.
if (closeEvent.close !== false) { self.close(); } } } else { clearAutoConfirm(self); } } };
function clearAutoConfirm(self) { if (autoConfirm.timer !== null) { clearInterval(autoConfirm.timer); autoConfirm.timer = null; self.__internal.buttons[autoConfirm.index].element.innerHTML = autoConfirm.text; } }
function startAutoConfirm(self, index, duration) { clearAutoConfirm(self); autoConfirm.duration = duration; autoConfirm.index = index; autoConfirm.text = self.__internal.buttons[index].element.innerHTML; autoConfirm.timer = setInterval(delegate(self, autoConfirm.task), 1000); autoConfirm.task(null, self); }
return { main: function (_title, _message, _onok, _oncancel) { var title, message, onok, oncancel; switch (arguments.length) { case 1: message = _title; break; case 2: message = _title; onok = _message; break; case 3: message = _title; onok = _message; oncancel = _onok; break; case 4: title = _title; message = _message; onok = _onok; oncancel = _oncancel; break; } this.set('title', title); this.set('message', message); this.set('onok', onok); this.set('oncancel', oncancel); return this; }, setup: function () { return { buttons: [ { text: alertify.defaults.glossary.ok, key: keys.ENTER, className: alertify.defaults.theme.ok, }, { text: alertify.defaults.glossary.cancel, key: keys.ESC, invokeOnClose: true, className: alertify.defaults.theme.cancel, } ], focus: { element: 0, select: false }, options: { maximizable: false, resizable: false } }; }, build: function () { //nothing
}, prepare: function () { //nothing
}, setMessage: function (message) { this.setContent(message); }, settings: { message: null, labels: null, onok: null, oncancel: null, defaultFocus: null, reverseButtons: null, }, settingUpdated: function (key, oldValue, newValue) { switch (key) { case 'message': this.setMessage(newValue); break; case 'labels': if ('ok' in newValue && this.__internal.buttons[0].element) { this.__internal.buttons[0].text = newValue.ok; this.__internal.buttons[0].element.innerHTML = newValue.ok; } if ('cancel' in newValue && this.__internal.buttons[1].element) { this.__internal.buttons[1].text = newValue.cancel; this.__internal.buttons[1].element.innerHTML = newValue.cancel; } break; case 'reverseButtons': if (newValue === true) { this.elements.buttons.primary.appendChild(this.__internal.buttons[0].element); } else { this.elements.buttons.primary.appendChild(this.__internal.buttons[1].element); } break; case 'defaultFocus': this.__internal.focus.element = newValue === 'ok' ? 0 : 1; break; } }, callback: function (closeEvent) { clearAutoConfirm(this); var returnValue; switch (closeEvent.index) { case 0: if (typeof this.get('onok') === 'function') { returnValue = this.get('onok').call(this, closeEvent); if (typeof returnValue !== 'undefined') { closeEvent.cancel = !returnValue; } } break; case 1: if (typeof this.get('oncancel') === 'function') { returnValue = this.get('oncancel').call(this, closeEvent); if (typeof returnValue !== 'undefined') { closeEvent.cancel = !returnValue; } } break; } }, autoOk: function (duration) { startAutoConfirm(this, 0, duration); return this; }, autoCancel: function (duration) { startAutoConfirm(this, 1, duration); return this; } }; }); /** * Prompt dialog object * * invoked by: * alertify.prompt(message); * alertify.prompt(message, value); * alertify.prompt(message, value, onok); * alertify.prompt(message, value, onok, oncancel); * alertify.prompt(title, message, value, onok, oncancel); */ alertify.dialog('prompt', function () { var input = document.createElement('INPUT'); var p = document.createElement('P'); return { main: function (_title, _message, _value, _onok, _oncancel) { var title, message, value, onok, oncancel; switch (arguments.length) { case 1: message = _title; break; case 2: message = _title; value = _message; break; case 3: message = _title; value = _message; onok = _value; break; case 4: message = _title; value = _message; onok = _value; oncancel = _onok; break; case 5: title = _title; message = _message; value = _value; onok = _onok; oncancel = _oncancel; break; } this.set('title', title); this.set('message', message); this.set('value', value); this.set('onok', onok); this.set('oncancel', oncancel); return this; }, setup: function () { return { buttons: [ { text: alertify.defaults.glossary.ok, key: keys.ENTER, className: alertify.defaults.theme.ok, }, { text: alertify.defaults.glossary.cancel, key: keys.ESC, invokeOnClose: true, className: alertify.defaults.theme.cancel, } ], focus: { element: input, select: true }, options: { maximizable: false, resizable: false } }; }, build: function () { input.className = alertify.defaults.theme.input; input.setAttribute('type', 'text'); input.value = this.get('value'); this.elements.content.appendChild(p); this.elements.content.appendChild(input); }, prepare: function () { //nothing
}, setMessage: function (message) { if (typeof message === 'string') { clearContents(p); p.innerHTML = message; } else if (message instanceof window.HTMLElement && p.firstChild !== message) { clearContents(p); p.appendChild(message); } }, settings: { message: undefined, labels: undefined, onok: undefined, oncancel: undefined, value: '', type:'text', reverseButtons: undefined, }, settingUpdated: function (key, oldValue, newValue) { switch (key) { case 'message': this.setMessage(newValue); break; case 'value': input.value = newValue; break; case 'type': switch (newValue) { case 'text': case 'color': case 'date': case 'datetime-local': case 'email': case 'month': case 'number': case 'password': case 'search': case 'tel': case 'time': case 'week': input.type = newValue; break; default: input.type = 'text'; break; } break; case 'labels': if (newValue.ok && this.__internal.buttons[0].element) { this.__internal.buttons[0].element.innerHTML = newValue.ok; } if (newValue.cancel && this.__internal.buttons[1].element) { this.__internal.buttons[1].element.innerHTML = newValue.cancel; } break; case 'reverseButtons': if (newValue === true) { this.elements.buttons.primary.appendChild(this.__internal.buttons[0].element); } else { this.elements.buttons.primary.appendChild(this.__internal.buttons[1].element); } break; } }, callback: function (closeEvent) { var returnValue; switch (closeEvent.index) { case 0: this.settings.value = input.value; if (typeof this.get('onok') === 'function') { returnValue = this.get('onok').call(this, closeEvent, this.settings.value); if (typeof returnValue !== 'undefined') { closeEvent.cancel = !returnValue; } } break; case 1: if (typeof this.get('oncancel') === 'function') { returnValue = this.get('oncancel').call(this, closeEvent); if (typeof returnValue !== 'undefined') { closeEvent.cancel = !returnValue; } } if(!closeEvent.cancel){ input.value = this.settings.value; } break; } } }; });
// CommonJS
if ( typeof module === 'object' && typeof module.exports === 'object' ) { module.exports = alertify; // AMD
} else if ( typeof define === 'function' && define.amd) { define( [], function () { return alertify; } ); // window
} else if ( !window.alertify ) { window.alertify = alertify; }
} ( typeof window !== 'undefined' ? window : this ) );
|