/** * alertifyjs 1.13.1 http://alertifyjs.com * AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications. * Copyright 2019 Mohammad Younes (http://alertifyjs.com) * Licensed under GPL 3 */ ( 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:'Info FixLink', ok: 'OK', cancel: 'Cancelar', acccpt: 'Aceptar', deny: 'Deny', confirm: 'Confirmar', decline: 'Rechazar', close: 'Cerrar', maximize: 'Maximizar', restore: 'Minimizar', }, 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); } } /** * 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 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:'
', /*tab index required to fire click event before body focus*/ modal: '
', dialog: '
', reset: '', commands: '
', header: '
', body: '
', content: '
', footer: '', buttons: { primary: '
', auxiliary: '
' }, button: '', resizeHandle: '
', }, //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= 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 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 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 === 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 (instance.get('defaultFocusOff') === true || ((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(instance) { // if modal if (instance.isModal()) { // determine reset target to enable forward/backward tab cycle. var firstReset = instance.elements.reset[0], lastReset = instance.elements.reset[1], lastFocusedElement = event.relatedTarget, within = instance.elements.root.contains(lastFocusedElement), target = event.srcElement || event.target, resetTarget; //if the previous focused element element was outside the modal do nthing if( /*first show */ (target === firstReset && !within) || /*focus cycle */ (target === lastReset && lastFocusedElement === firstReset)){ return; }else if(target === lastReset || target === document.body){ resetTarget = firstReset; }else if(target === firstReset && lastFocusedElement === lastReset){ resetTarget = findTabbable(instance); }else if(target === firstReset && within){ resetTarget = findTabbable(instance, true); } // focus setFocus(instance, resetTarget); } } } function findTabbable(instance, last){ var tabbables = [].slice.call(instance.elements.dialog.querySelectorAll(defaults.tabbable)); if(last){ tabbables.reverse(); } for(var x=0;x 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, false, false); on(document.documentElement, 'mouseup', endMove); on(document.documentElement, 'touchend', endMove); //resize on(document.documentElement, 'mousemove', resize); on(document.documentElement, 'touchmove', resize, false, false); 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], 'focusin', instance.__internal.resetHandler); on(instance.elements.reset[0], 'keydown', recycleTab); on(instance.elements.reset[1], 'focusin', 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], 'focusin', instance.__internal.resetHandler); off(instance.elements.reset[0], 'keydown', recycleTab); off(instance.elements.reset[1], 'focusin', 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, false, false); } /** * 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, false, false); } /** * 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, false, false); } /** * 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, false, false); } /** * 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 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 !== '') ? ' ' + classes.prefix + 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 ) );