MySC/static/lib/alertify/alertify.js

3661 lines
140 KiB
JavaScript
Raw Permalink Normal View History

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