Commit 9b6abe45 authored by bingchuan's avatar bingchuan

Update alertify.js

parent a5f50604
/** /**
* alertifyjs 1.8.0 http://alertifyjs.com * alertifyjs 1.14.0 http://alertifyjs.com
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications. * AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
* Copyright 2016 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com) * Copyright 2024 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/ * Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
( function ( window ) { ( function ( window ) {
'use strict'; 'use strict';
var NOT_DISABLED_NOT_RESET = ':not(:disabled):not(.ajs-reset)';
/** /**
* Keys enum * Keys enum
* @type {Object} * @type {Object}
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
F1: 112, F1: 112,
F12: 123, F12: 123,
LEFT: 37, LEFT: 37,
RIGHT: 39 RIGHT: 39,
TAB: 9
}; };
/** /**
* Default options * Default options
...@@ -27,7 +28,9 @@ ...@@ -27,7 +28,9 @@
basic:false, basic:false,
closable:true, closable:true,
closableByDimmer:true, closableByDimmer:true,
invokeOnCloseOff:false,
frameless:false, frameless:false,
defaultFocusOff:false,
maintainFocus:true, //global default not per instance, applies to all dialogs maintainFocus:true, //global default not per instance, applies to all dialogs
maximizable:true, maximizable:true,
modal:true, modal:true,
...@@ -41,9 +44,25 @@ ...@@ -41,9 +44,25 @@
resizable:true, resizable:true,
startMaximized:false, startMaximized:false,
transition:'pulse', transition:'pulse',
transitionOff:false,
tabbable:['button', '[href]', 'input', 'select', 'textarea', '[tabindex]:not([tabindex^="-"])'+NOT_DISABLED_NOT_RESET].join(NOT_DISABLED_NOT_RESET+','),//global
notifier:{ notifier:{
delay:5, delay:5,
position:'bottom-right' 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:{ glossary:{
title:'AlertifyJS', title:'AlertifyJS',
...@@ -61,6 +80,10 @@ ...@@ -61,6 +80,10 @@
input:'ajs-input', input:'ajs-input',
ok:'ajs-ok', ok:'ajs-ok',
cancel:'ajs-cancel', cancel:'ajs-cancel',
},
hooks:{
preinit:function(){},
postinit:function(){}
} }
}; };
...@@ -134,6 +157,16 @@ ...@@ -134,6 +157,16 @@
element.removeChild(element.lastChild); element.removeChild(element.lastChild);
} }
} }
/**
* detects strings, checks for both string and String instances
* this is unlike typeof(x) === 'string' which only accepts primitive strings
*
*/
function isString(thing) {
return Object.prototype.toString.call(thing) === '[object String]';
}
/** /**
* Extends a given prototype by merging properties from base into sub. * Extends a given prototype by merging properties from base into sub.
* *
...@@ -185,63 +218,59 @@ ...@@ -185,63 +218,59 @@
* *
*/ */
function destruct(instance, initialize){ function destruct(instance, initialize){
//delete the dom and it's references. if(instance.elements){
var root = instance.elements.root; //delete the dom and it's references.
root.parentNode.removeChild(root); var root = instance.elements.root;
delete instance.elements; root.parentNode.removeChild(root);
//copy back initial settings. delete instance.elements;
instance.settings = copy(instance.__settings); //copy back initial settings.
//re-reference init function. instance.settings = copy(instance.__settings);
instance.__init = initialize; //re-reference init function.
//delete __internal variable to allow re-initialization. instance.__init = initialize;
delete instance.__internal; //delete __internal variable to allow re-initialization.
delete instance.__internal;
}
} }
/** /**
* Use a closure to return proper event listener method. Try to use * Test to check if passive event listeners are supported.
* `addEventListener` by default but fallback to `attachEvent` for */
* unsupported browser. The closure simply ensures that the test doesn't var IsPassiveSupported = false;
* happen every time the method is called. 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 {Node} el Node element * @param {HTMLElement} el The EventTarget to register the listenr on.
* @param {String} event Event type * @param {string} event The event type to listen for.
* @param {Function} fn Callback of event * @param {Function} handler The function to handle the event.
* @return {Function} * @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 () { var on = function (el, event, fn, useCapture, passive) {
if (document.addEventListener) { el.addEventListener(event, fn, IsPassiveSupported ? { capture: useCapture, passive: passive } : useCapture === true);
return function (el, event, fn, useCapture) { };
el.addEventListener(event, fn, useCapture === true);
};
} else if (document.attachEvent) {
return function (el, event, fn) {
el.attachEvent('on' + event, fn);
};
}
}());
/** /**
* Use a closure to return proper event listener method. Try to use * Removes an event listener
* `removeEventListener` by default but fallback to `detachEvent` for
* unsupported browser. The closure simply ensures that the test doesn't
* happen every time the method is called.
* *
* @param {Node} el Node element * @param {HTMLElement} el The EventTarget to unregister the listenr from.
* @param {String} event Event type * @param {string} event The event type to remove.
* @param {Function} fn Callback of event * @param {Function} fn The event handler to remove.
* @return {Function} * @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 () { var off = function (el, event, fn, useCapture, passive) {
if (document.removeEventListener) { el.removeEventListener(event, fn, IsPassiveSupported ? { capture: useCapture, passive: passive } : useCapture === true);
return function (el, event, fn, useCapture) { };
el.removeEventListener(event, fn, useCapture === true);
};
} else if (document.detachEvent) {
return function (el, event, fn) {
el.detachEvent('on' + event, fn);
};
}
}());
/** /**
* Prevent default event from firing * Prevent default event from firing
...@@ -250,14 +279,14 @@ ...@@ -250,14 +279,14 @@
* @return {undefined} * @return {undefined}
function prevent ( event ) { function prevent ( event ) {
if ( event ) { if ( event ) {
if ( event.preventDefault ) { if ( event.preventDefault ) {
event.preventDefault(); event.preventDefault();
} else { } else {
event.returnValue = false; event.returnValue = false;
} }
} }
} }
*/ */
var transition = (function () { var transition = (function () {
var t, type; var t, type;
...@@ -320,11 +349,11 @@ ...@@ -320,11 +349,11 @@
* @param {string} evenType The type of the event to disptach. * @param {string} evenType The type of the event to disptach.
* @param {object} instance The dialog instance disptaching the event. * @param {object} instance The dialog instance disptaching the event.
* *
* @return {object} * @return {any} The result of the invoked function.
*/ */
function dispatchEvent(eventType, instance) { function dispatchEvent(eventType, instance) {
if ( typeof instance.get(eventType) === 'function' ) { if ( typeof instance.get(eventType) === 'function' ) {
instance.get(eventType).call(instance); return instance.get(eventType).call(instance);
} }
} }
...@@ -336,54 +365,57 @@ ...@@ -336,54 +365,57 @@
*/ */
var dialog = (function () { var dialog = (function () {
var //holds the list of used keys. var //holds the list of used keys.
usedKeys = [], usedKeys = [],
//dummy variable, used to trigger dom reflow. //dummy variable, used to trigger dom reflow.
reflow = null, reflow = null,
//condition for detecting safari //holds body tab index in case it has any.
isSafari = window.navigator.userAgent.indexOf('Safari') > -1 && window.navigator.userAgent.indexOf('Chrome') < 0, tabindex = false,
//dialog building blocks //condition for detecting safari
templates = { isSafari = window.navigator.userAgent.indexOf('Safari') > -1 && window.navigator.userAgent.indexOf('Chrome') < 0,
dimmer:'<div class="ajs-dimmer"></div>', //dialog building blocks
/*tab index required to fire click event before body focus*/ templates = {
modal: '<div class="ajs-modal" tabindex="0"></div>', dimmer:'<div class="ajs-dimmer"></div>',
dialog: '<div class="ajs-dialog" tabindex="0"></div>', /*tab index required to fire click event before body focus*/
reset: '<button class="ajs-reset"></button>', modal: '<div class="ajs-modal" tabindex="0"></div>',
commands: '<div class="ajs-commands"><button class="ajs-pin"></button><button class="ajs-maximize"></button><button class="ajs-close"></button></div>', dialog: '<div class="ajs-dialog" tabindex="0"></div>',
header: '<div class="ajs-header"></div>', reset: '<button class="ajs-reset"></button>',
body: '<div class="ajs-body"></div>', commands: '<div class="ajs-commands"><button class="ajs-pin"></button><button class="ajs-maximize"></button><button class="ajs-close"></button></div>',
content: '<div class="ajs-content"></div>', header: '<div class="ajs-header"></div>',
footer: '<div class="ajs-footer"></div>', body: '<div class="ajs-body"></div>',
buttons: { primary: '<div class="ajs-primary ajs-buttons"></div>', auxiliary: '<div class="ajs-auxiliary ajs-buttons"></div>' }, content: '<div class="ajs-content"></div>',
button: '<button class="ajs-button"></button>', footer: '<div class="ajs-footer"></div>',
resizeHandle: '<div class="ajs-handle"></div>', buttons: { primary: '<div class="ajs-primary ajs-buttons"></div>', auxiliary: '<div class="ajs-auxiliary ajs-buttons"></div>' },
}, button: '<button class="ajs-button"></button>',
//common class names resizeHandle: '<div class="ajs-handle"></div>',
classes = { },
animationIn: 'ajs-in', //common class names
animationOut: 'ajs-out', classes = {
base: 'alertify', animationIn: 'ajs-in',
basic:'ajs-basic', animationOut: 'ajs-out',
capture: 'ajs-capture', base: 'alertify',
closable:'ajs-closable', basic:'ajs-basic',
fixed: 'ajs-fixed', capture: 'ajs-capture',
frameless:'ajs-frameless', closable:'ajs-closable',
hidden: 'ajs-hidden', fixed: 'ajs-fixed',
maximize: 'ajs-maximize', frameless:'ajs-frameless',
maximized: 'ajs-maximized', hidden: 'ajs-hidden',
maximizable:'ajs-maximizable', maximize: 'ajs-maximize',
modeless: 'ajs-modeless', maximized: 'ajs-maximized',
movable: 'ajs-movable', maximizable:'ajs-maximizable',
noSelection: 'ajs-no-selection', modeless: 'ajs-modeless',
noOverflow: 'ajs-no-overflow', movable: 'ajs-movable',
noPadding:'ajs-no-padding', noSelection: 'ajs-no-selection',
pin:'ajs-pin', noOverflow: 'ajs-no-overflow',
pinnable:'ajs-pinnable', noPadding:'ajs-no-padding',
prefix: 'ajs-', pin:'ajs-pin',
resizable: 'ajs-resizable', pinnable:'ajs-pinnable',
restore: 'ajs-restore', prefix: 'ajs-',
shake:'ajs-shake', resizable: 'ajs-resizable',
unpinned:'ajs-unpinned', restore: 'ajs-restore',
}; shake:'ajs-shake',
unpinned:'ajs-unpinned',
noTransition:'ajs-no-transition'
};
/** /**
* Helper: initializes the dialog instance * Helper: initializes the dialog instance
...@@ -392,8 +424,9 @@ ...@@ -392,8 +424,9 @@
*/ */
function initialize(instance){ function initialize(instance){
if(!instance.__internal||instance.settings.type=='text'){ if(!instance.__internal){
//invoke preinit global hook
alertify.defaults.hooks.preinit(instance);
//no need to expose init after this. //no need to expose init after this.
delete instance.__init; delete instance.__init;
...@@ -401,13 +434,6 @@ ...@@ -401,13 +434,6 @@
if(!instance.__settings){ if(!instance.__settings){
instance.__settings = copy(instance.settings); instance.__settings = copy(instance.settings);
} }
//in case the script was included before body.
//after first dialog gets initialized, it won't be null anymore!
if(null === reflow){
// set tabindex attribute on body element this allows script to give it
// focus after the dialog is closed
document.body.setAttribute( 'tabindex', '0' );
}
//get dialog buttons/focus setup //get dialog buttons/focus setup
var setup; var setup;
...@@ -437,7 +463,7 @@ ...@@ -437,7 +463,7 @@
if(Array.isArray(setup.buttons)){ if(Array.isArray(setup.buttons)){
for(var b=0;b<setup.buttons.length;b+=1){ for(var b=0;b<setup.buttons.length;b+=1){
var ref = setup.buttons[b], var ref = setup.buttons[b],
cpy = {}; cpy = {};
for (var i in ref) { for (var i in ref) {
if (ref.hasOwnProperty(i)) { if (ref.hasOwnProperty(i)) {
cpy[i] = ref[i]; cpy[i] = ref[i];
...@@ -471,6 +497,7 @@ ...@@ -471,6 +497,7 @@
modal: undefined, modal: undefined,
basic:undefined, basic:undefined,
frameless:undefined, frameless:undefined,
defaultFocusOff:undefined,
pinned: undefined, pinned: undefined,
movable: undefined, movable: undefined,
moveBounded:undefined, moveBounded:undefined,
...@@ -478,13 +505,16 @@ ...@@ -478,13 +505,16 @@
autoReset: undefined, autoReset: undefined,
closable: undefined, closable: undefined,
closableByDimmer: undefined, closableByDimmer: undefined,
invokeOnCloseOff:undefined,
maximizable: undefined, maximizable: undefined,
startMaximized: undefined, startMaximized: undefined,
pinnable: undefined, pinnable: undefined,
transition: undefined, transition: undefined,
transitionOff: undefined,
padding:undefined, padding:undefined,
overflow:undefined, overflow:undefined,
onshow:undefined, onshow:undefined,
onclosing:undefined,
onclose:undefined, onclose:undefined,
onfocus:undefined, onfocus:undefined,
onmove:undefined, onmove:undefined,
...@@ -511,7 +541,8 @@ ...@@ -511,7 +541,8 @@
var elements = {}; var elements = {};
//root node //root node
elements.root = document.createElement('div'); 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.className = classes.base + ' ' + classes.hidden + ' ';
elements.root.innerHTML = templates.dimmer + templates.modal; elements.root.innerHTML = templates.dimmer + templates.modal;
...@@ -618,6 +649,9 @@ ...@@ -618,6 +649,9 @@
if(typeof instance.build === 'function'){ if(typeof instance.build === 'function'){
instance.build(); instance.build();
} }
//invoke postinit global hook
alertify.defaults.hooks.postinit(instance);
} }
//add to the end of the DOM tree. //add to the end of the DOM tree.
...@@ -665,13 +699,13 @@ ...@@ -665,13 +699,13 @@
* *
*/ */
function preventBodyShift(add){ function preventBodyShift(add){
if(alertify.defaults.preventBodyShift && document.documentElement.scrollHeight > document.documentElement.clientHeight){ if(alertify.defaults.preventBodyShift){
if(add ){//&& openDialogs[openDialogs.length-1].elements.dialog.clientHeight <= document.documentElement.clientHeight){ if(add && document.documentElement.scrollHeight > document.documentElement.clientHeight ){//&& openDialogs[openDialogs.length-1].elements.dialog.clientHeight <= document.documentElement.clientHeight){
topScroll = scrollY; topScroll = scrollY;
top = window.getComputedStyle(document.body).top; top = window.getComputedStyle(document.body).top;
addClass(document.body, classes.fixed); addClass(document.body, classes.fixed);
document.body.style.top = -scrollY + 'px'; document.body.style.top = -scrollY + 'px';
} else { } else if(!add) {
scrollY = topScroll; scrollY = topScroll;
document.body.style.top = top; document.body.style.top = top;
removeClass(document.body, classes.fixed); removeClass(document.body, classes.fixed);
...@@ -687,13 +721,30 @@ ...@@ -687,13 +721,30 @@
* *
*/ */
function updateTransition(instance, value, oldValue){ function updateTransition(instance, value, oldValue){
if(typeof oldValue === 'string'){ if(isString(oldValue)){
removeClass(instance.elements.root,classes.prefix + oldValue); removeClass(instance.elements.root,classes.prefix + oldValue);
} }
addClass(instance.elements.root, classes.prefix + value); addClass(instance.elements.root, classes.prefix + value);
reflow = instance.elements.root.offsetWidth; 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 * Toggles the dialog display mode
* *
...@@ -836,9 +887,6 @@ ...@@ -836,9 +887,6 @@
case 'resizable': case 'resizable':
updateResizable(instance); updateResizable(instance);
break; break;
case 'transition':
updateTransition(instance,newValue, oldValue);
break;
case 'padding': case 'padding':
if(newValue){ if(newValue){
removeClass(instance.elements.root, classes.noPadding); removeClass(instance.elements.root, classes.noPadding);
...@@ -853,7 +901,12 @@ ...@@ -853,7 +901,12 @@
addClass(instance.elements.root, classes.noOverflow); addClass(instance.elements.root, classes.noOverflow);
} }
break; break;
case 'transition':
updateTransition(instance,newValue, oldValue);
break;
case 'transitionOff':
updateTransitionOff(instance);
break;
} }
// internal on option updated event // internal on option updated event
...@@ -949,7 +1002,7 @@ ...@@ -949,7 +1002,7 @@
function triggerClose(instance) { function triggerClose(instance) {
var found; var found;
triggerCallback(instance, function (button) { triggerCallback(instance, function (button) {
return found = (button.invokeOnClose === true); return found = instance.get('invokeOnCloseOff') !== true && (button.invokeOnClose === true);
}); });
//none of the buttons registered as onclose callback //none of the buttons registered as onclose callback
//close the dialog //close the dialog
...@@ -1101,7 +1154,7 @@ ...@@ -1101,7 +1154,7 @@
if (instance.isOpen()) { if (instance.isOpen()) {
var top = 0, var top = 0,
left = 0 left = 0
; ;
if (instance.elements.dialog.style.top !== '') { if (instance.elements.dialog.style.top !== '') {
top = parseInt(instance.elements.dialog.style.top, 10); top = parseInt(instance.elements.dialog.style.top, 10);
...@@ -1189,8 +1242,10 @@ ...@@ -1189,8 +1242,10 @@
} }
} }
// flag to cancel click event if already handled by end resize event (the mousedown, mousemove, mouseup sequence fires a click event.).
var cancelClick = false; 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 * Helper: closes the modal dialog when clicking the modal
...@@ -1201,14 +1256,17 @@ ...@@ -1201,14 +1256,17 @@
* @return {undefined} * @return {undefined}
*/ */
function modalClickHandler(event, instance) { function modalClickHandler(event, instance) {
var target = event.srcElement || event.target; if(event.timeStamp - modalClickHandlerTS > 200 && (modalClickHandlerTS = event.timeStamp) && !cancelClick){
if (!cancelClick && target === instance.elements.modal && instance.get('closableByDimmer') === true) { var target = event.srcElement || event.target;
triggerClose(instance); if (instance.get('closableByDimmer') === true && target === instance.elements.modal) {
triggerClose(instance);
}
} }
cancelClick = false; cancelClick = false;
return false;
} }
// stores last call timestamp to prevent triggering the callback twice.
var callbackTS = 0;
// flag to cancel keyup event if already handled by click event (pressing Enter on a focusted button). // flag to cancel keyup event if already handled by click event (pressing Enter on a focusted button).
var cancelKeyup = false; var cancelKeyup = false;
/** /**
...@@ -1220,18 +1278,20 @@ ...@@ -1220,18 +1278,20 @@
* @return {undefined} * @return {undefined}
*/ */
function triggerCallback(instance, check) { function triggerCallback(instance, check) {
for (var idx = 0; idx < instance.__internal.buttons.length; idx += 1) { if(Date.now() - callbackTS > 200 && (callbackTS = Date.now())){
var button = instance.__internal.buttons[idx]; for (var idx = 0; idx < instance.__internal.buttons.length; idx += 1) {
if (!button.element.disabled && check(button)) { var button = instance.__internal.buttons[idx];
var closeEvent = createCloseEvent(idx, button); if (!button.element.disabled && check(button)) {
if (typeof instance.callback === 'function') { var closeEvent = createCloseEvent(idx, button);
instance.callback.apply(instance, [closeEvent]); if (typeof instance.callback === 'function') {
} instance.callback.apply(instance, [closeEvent]);
//close the dialog only if not canceled. }
if (closeEvent.cancel === false) { //close the dialog only if not canceled.
instance.close(); if (closeEvent.cancel === false) {
instance.close();
}
break;
} }
break;
} }
} }
} }
...@@ -1248,7 +1308,7 @@ ...@@ -1248,7 +1308,7 @@
var target = event.srcElement || event.target; var target = event.srcElement || event.target;
triggerCallback(instance, function (button) { triggerCallback(instance, function (button) {
// if this button caused the click, cancel keyup event // if this button caused the click, cancel keyup event
return button.element === target && (cancelKeyup = true); return button.element.contains(target) && (cancelKeyup = true);
}); });
} }
...@@ -1334,7 +1394,7 @@ ...@@ -1334,7 +1394,7 @@
var element = focus.element; var element = focus.element;
switch (typeof focus.element) { switch (typeof focus.element) {
// a number means a button index // a number means a button index
case 'number': case 'number':
if (instance.__internal.buttons.length > focus.element) { if (instance.__internal.buttons.length > focus.element) {
//in basic view, skip focusing the buttons. //in basic view, skip focusing the buttons.
...@@ -1345,18 +1405,18 @@ ...@@ -1345,18 +1405,18 @@
} }
} }
break; break;
// a string means querySelector to select from dialog body contents. // a string means querySelector to select from dialog body contents.
case 'string': case 'string':
element = instance.elements.body.querySelector(focus.element); element = instance.elements.body.querySelector(focus.element);
break; break;
// a function should return the focus element. // a function should return the focus element.
case 'function': case 'function':
element = focus.element.call(instance); element = focus.element.call(instance);
break; break;
} }
// if no focus element, default to first reset element. // if no focus element, default to first reset element.
if ((typeof element === 'undefined' || element === null) && instance.__internal.buttons.length === 0) { if (instance.get('defaultFocusOff') === true || ((typeof element === 'undefined' || element === null) && instance.__internal.buttons.length === 0)) {
element = instance.elements.reset[0]; element = instance.elements.reset[0];
} }
// focus // focus
...@@ -1390,40 +1450,53 @@ ...@@ -1390,40 +1450,53 @@
} }
} }
} }
// if modal
if (instance && instance.isModal()) {
// determine reset target to enable forward/backward tab cycle.
var resetTarget, target = event.srcElement || event.target;
var lastResetElement = target === instance.elements.reset[1] || (instance.__internal.buttons.length === 0 && target === document.body);
// if last reset link, then go to maximize or close if(instance) {
if (lastResetElement) { // if modal
if (instance.get('maximizable')) { if (instance.isModal()) {
resetTarget = instance.elements.commands.maximize; // determine reset target to enable forward/backward tab cycle.
} else if (instance.get('closable')) { var firstReset = instance.elements.reset[0],
resetTarget = instance.elements.commands.close; 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);
} }
// if no reset target found, try finding the best button }
if (resetTarget === undefined) { }
if (typeof instance.__internal.focus.element === 'number') { function findTabbable(instance, last){
// button focus element, go to first available button var tabbables = [].slice.call(instance.elements.dialog.querySelectorAll(defaults.tabbable));
if (target === instance.elements.reset[0]) { if(last){
resetTarget = instance.elements.buttons.auxiliary.firstChild || instance.elements.buttons.primary.firstChild; tabbables.reverse();
} else if (lastResetElement) { }
//restart the cycle by going to first reset link for(var x=0;x<tabbables.length;x+=1){
resetTarget = instance.elements.reset[0]; var tabbable = tabbables[x];
} //check if visible
} else { if(!!(tabbable.offsetParent || tabbable.offsetWidth || tabbable.offsetHeight || tabbable.getClientRects().length)){
// will reach here when tapping backwards, so go to last child return tabbable;
// The focus element SHOULD NOT be a button (logically!).
if (target === instance.elements.reset[0]) {
resetTarget = instance.elements.buttons.primary.lastChild || instance.elements.buttons.auxiliary.lastChild;
}
}
} }
// focus }
setFocus(instance, resetTarget); }
function recycleTab(event) {
var instance = openDialogs[openDialogs.length - 1];
if (instance && event.shiftKey && event.keyCode === keys.TAB) {
instance.elements.reset[1].focus();
} }
} }
/** /**
...@@ -1441,9 +1514,6 @@ ...@@ -1441,9 +1514,6 @@
// once transition is complete, set focus // once transition is complete, set focus
setFocus(instance); setFocus(instance);
//restore scroll to prevent document jump
restoreScrollPosition();
// allow handling key up after transition ended. // allow handling key up after transition ended.
cancelKeyup = false; cancelKeyup = false;
...@@ -1480,12 +1550,6 @@ ...@@ -1480,12 +1550,6 @@
restore(instance); restore(instance);
} }
// return focus to the last active element
if (alertify.defaults.maintainFocus && instance.__internal.activeElement) {
instance.__internal.activeElement.focus();
instance.__internal.activeElement = null;
}
//destory the instance //destory the instance
if (typeof instance.__internal.destroy === 'function') { if (typeof instance.__internal.destroy === 'function') {
instance.__internal.destroy.apply(instance); instance.__internal.destroy.apply(instance);
...@@ -1494,15 +1558,15 @@ ...@@ -1494,15 +1558,15 @@
/* Controls moving a dialog around */ /* Controls moving a dialog around */
//holde the current moving instance //holde the current moving instance
var movable = null, var movable = null,
//holds the current X offset when move starts //holds the current X offset when move starts
offsetX = 0, offsetX = 0,
//holds the current Y offset when move starts //holds the current Y offset when move starts
offsetY = 0, offsetY = 0,
xProp = 'pageX', xProp = 'pageX',
yProp = 'pageY', yProp = 'pageY',
bounds = null, bounds = null,
refreshTop = false, refreshTop = false,
moveDelegate = null moveDelegate = null
; ;
/** /**
...@@ -1515,7 +1579,7 @@ ...@@ -1515,7 +1579,7 @@
*/ */
function moveElement(event, element) { function moveElement(event, element) {
var left = (event[xProp] - offsetX), var left = (event[xProp] - offsetX),
top = (event[yProp] - offsetY); top = (event[yProp] - offsetY);
if(refreshTop){ if(refreshTop){
top -= document.body.scrollTop; top -= document.body.scrollTop;
...@@ -1535,7 +1599,7 @@ ...@@ -1535,7 +1599,7 @@
*/ */
function moveElementBounded(event, element) { function moveElementBounded(event, element) {
var left = (event[xProp] - offsetX), var left = (event[xProp] - offsetX),
top = (event[yProp] - offsetY); top = (event[yProp] - offsetY);
if(refreshTop){ if(refreshTop){
top -= document.body.scrollTop; top -= document.body.scrollTop;
...@@ -1595,8 +1659,8 @@ ...@@ -1595,8 +1659,8 @@
if(instance.get('moveBounded')){ if(instance.get('moveBounded')){
var current = element, var current = element,
offsetLeft = -left, offsetLeft = -left,
offsetTop = -top; offsetTop = -top;
//calc offset //calc offset
do { do {
...@@ -1710,14 +1774,14 @@ ...@@ -1710,14 +1774,14 @@
/* Controls moving a dialog around */ /* Controls moving a dialog around */
//holde the current instance being resized //holde the current instance being resized
var resizable = null, var resizable = null,
//holds the staring left offset when resize starts. //holds the staring left offset when resize starts.
startingLeft = Number.Nan, startingLeft = Number.Nan,
//holds the staring width when resize starts. //holds the staring width when resize starts.
startingWidth = 0, startingWidth = 0,
//holds the initial width when resized for the first time. //holds the initial width when resized for the first time.
minWidth = 0, minWidth = 0,
//holds the offset of the resize handle. //holds the offset of the resize handle.
handleOffset = 0 handleOffset = 0
; ;
/** /**
...@@ -1945,12 +2009,12 @@ ...@@ -1945,12 +2009,12 @@
//move //move
on(document.documentElement, 'mousemove', move); on(document.documentElement, 'mousemove', move);
on(document.documentElement, 'touchmove', move); on(document.documentElement, 'touchmove', move, false, false);
on(document.documentElement, 'mouseup', endMove); on(document.documentElement, 'mouseup', endMove);
on(document.documentElement, 'touchend', endMove); on(document.documentElement, 'touchend', endMove);
//resize //resize
on(document.documentElement, 'mousemove', resize); on(document.documentElement, 'mousemove', resize);
on(document.documentElement, 'touchmove', resize); on(document.documentElement, 'touchmove', resize, false, false);
on(document.documentElement, 'mouseup', endResize); on(document.documentElement, 'mouseup', endResize);
on(document.documentElement, 'touchend', endResize); on(document.documentElement, 'touchend', endResize);
} }
...@@ -1958,8 +2022,9 @@ ...@@ -1958,8 +2022,9 @@
// common events // common events
on(instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler); on(instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler);
on(instance.elements.footer, 'click', instance.__internal.buttonsClickHandler); on(instance.elements.footer, 'click', instance.__internal.buttonsClickHandler);
on(instance.elements.reset[0], 'focus', instance.__internal.resetHandler); on(instance.elements.reset[0], 'focusin', instance.__internal.resetHandler);
on(instance.elements.reset[1], 'focus', 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. //prevent handling key up when dialog is being opened by a key stroke.
cancelKeyup = true; cancelKeyup = true;
...@@ -2008,8 +2073,9 @@ ...@@ -2008,8 +2073,9 @@
// common events // common events
off(instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler); off(instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler);
off(instance.elements.footer, 'click', instance.__internal.buttonsClickHandler); off(instance.elements.footer, 'click', instance.__internal.buttonsClickHandler);
off(instance.elements.reset[0], 'focus', instance.__internal.resetHandler); off(instance.elements.reset[0], 'focusin', instance.__internal.resetHandler);
off(instance.elements.reset[1], 'focus', instance.__internal.resetHandler); off(instance.elements.reset[0], 'keydown', recycleTab);
off(instance.elements.reset[1], 'focusin', instance.__internal.resetHandler);
// hook out transition handler // hook out transition handler
on(instance.elements.dialog, transition.type, instance.__internal.transitionOutHandler); on(instance.elements.dialog, transition.type, instance.__internal.transitionOutHandler);
...@@ -2064,7 +2130,7 @@ ...@@ -2064,7 +2130,7 @@
*/ */
function bindMovableEvents(instance) { function bindMovableEvents(instance) {
on(instance.elements.header, 'mousedown', instance.__internal.beginMoveHandler); on(instance.elements.header, 'mousedown', instance.__internal.beginMoveHandler);
on(instance.elements.header, 'touchstart', instance.__internal.beginMoveHandler); on(instance.elements.header, 'touchstart', instance.__internal.beginMoveHandler, false, false);
} }
/** /**
...@@ -2076,7 +2142,7 @@ ...@@ -2076,7 +2142,7 @@
*/ */
function unbindMovableEvents(instance) { function unbindMovableEvents(instance) {
off(instance.elements.header, 'mousedown', instance.__internal.beginMoveHandler); off(instance.elements.header, 'mousedown', instance.__internal.beginMoveHandler);
off(instance.elements.header, 'touchstart', instance.__internal.beginMoveHandler); off(instance.elements.header, 'touchstart', instance.__internal.beginMoveHandler, false, false);
} }
...@@ -2090,7 +2156,7 @@ ...@@ -2090,7 +2156,7 @@
*/ */
function bindResizableEvents(instance) { function bindResizableEvents(instance) {
on(instance.elements.resizeHandle, 'mousedown', instance.__internal.beginResizeHandler); on(instance.elements.resizeHandle, 'mousedown', instance.__internal.beginResizeHandler);
on(instance.elements.resizeHandle, 'touchstart', instance.__internal.beginResizeHandler); on(instance.elements.resizeHandle, 'touchstart', instance.__internal.beginResizeHandler, false, false);
} }
/** /**
...@@ -2102,7 +2168,7 @@ ...@@ -2102,7 +2168,7 @@
*/ */
function unbindResizableEvents(instance) { function unbindResizableEvents(instance) {
off(instance.elements.resizeHandle, 'mousedown', instance.__internal.beginResizeHandler); off(instance.elements.resizeHandle, 'mousedown', instance.__internal.beginResizeHandler);
off(instance.elements.resizeHandle, 'touchstart', instance.__internal.beginResizeHandler); off(instance.elements.resizeHandle, 'touchstart', instance.__internal.beginResizeHandler, false, false);
} }
/** /**
...@@ -2188,9 +2254,9 @@ ...@@ -2188,9 +2254,9 @@
dispatchEvent('onmove', this); dispatchEvent('onmove', this);
var element = this.elements.dialog, var element = this.elements.dialog,
current = element, current = element,
offsetLeft = 0, offsetLeft = 0,
offsetTop = 0; offsetTop = 0;
//subtract existing left,top //subtract existing left,top
if (element.style.left) { if (element.style.left) {
...@@ -2236,8 +2302,8 @@ ...@@ -2236,8 +2302,8 @@
*/ */
resizeTo:function(width,height){ resizeTo:function(width,height){
var w = parseFloat(width), var w = parseFloat(width),
h = parseFloat(height), h = parseFloat(height),
regex = /(\d*\.\d+|\d+)%/ regex = /(\d*\.\d+|\d+)%/
; ;
if(!isNaN(w) && !isNaN(h) && this.get('resizable') === true){ if(!isNaN(w) && !isNaN(h) && this.get('resizable') === true){
...@@ -2319,7 +2385,7 @@ ...@@ -2319,7 +2385,7 @@
* @return {undefined} * @return {undefined}
*/ */
setHeader:function(content){ setHeader:function(content){
if(typeof content === 'string'){ if(isString(content)){
clearContents(this.elements.header); clearContents(this.elements.header);
this.elements.header.innerHTML = content; this.elements.header.innerHTML = content;
}else if (content instanceof window.HTMLElement && this.elements.header.firstChild !== content){ }else if (content instanceof window.HTMLElement && this.elements.header.firstChild !== content){
...@@ -2335,7 +2401,7 @@ ...@@ -2335,7 +2401,7 @@
* @return {undefined} * @return {undefined}
*/ */
setContent:function(content){ setContent:function(content){
if(typeof content === 'string'){ if(isString(content)){
clearContents(this.elements.content); clearContents(this.elements.content);
this.elements.content.innerHTML = content; this.elements.content.innerHTML = content;
}else if (content instanceof window.HTMLElement && this.elements.content.firstChild !== content){ }else if (content instanceof window.HTMLElement && this.elements.content.firstChild !== content){
...@@ -2373,6 +2439,11 @@ ...@@ -2373,6 +2439,11 @@
this.__internal.activeElement = document.activeElement; 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. //allow custom dom manipulation updates before showing the dialog.
if(typeof this.prepare === 'function'){ if(typeof this.prepare === 'function'){
this.prepare(); this.prepare();
...@@ -2403,7 +2474,7 @@ ...@@ -2403,7 +2474,7 @@
} }
updateAbsPositionFix(this); updateAbsPositionFix(this);
this.elements.root.removeAttribute('style');
removeClass(this.elements.root, classes.animationOut); removeClass(this.elements.root, classes.animationOut);
addClass(this.elements.root, classes.animationIn); addClass(this.elements.root, classes.animationIn);
...@@ -2424,6 +2495,9 @@ ...@@ -2424,6 +2495,9 @@
// show dialog // show dialog
removeClass(this.elements.root, classes.hidden); removeClass(this.elements.root, classes.hidden);
//restore scroll to prevent document jump
restoreScrollPosition();
// internal on show event // internal on show event
if(typeof this.hooks.onshow === 'function'){ if(typeof this.hooks.onshow === 'function'){
this.hooks.onshow.call(this); this.hooks.onshow.call(this);
...@@ -2453,39 +2527,52 @@ ...@@ -2453,39 +2527,52 @@
*/ */
close: function () { close: function () {
if (this.__internal.isOpen ) { 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;
}
unbindEvents(this); // remove custom dialog class on hide
if (typeof this.__internal.className !== 'undefined' && this.__internal.className !== '') {
removeClass(this.elements.root, this.__internal.className);
}
removeClass(this.elements.root, classes.animationIn); // internal on close event
addClass(this.elements.root, classes.animationOut); if(typeof this.hooks.onclose === 'function'){
this.hooks.onclose.call(this);
}
// set 1s fallback in case transition event doesn't fire // allow custom `onclose` method
clearTimeout( this.__internal.timerOut ); dispatchEvent('onclose', this);
this.__internal.timerOut = setTimeout( this.__internal.transitionOutHandler, transition.supported ? 1000 : 100 );
// hide dialog
addClass(this.elements.root, classes.hidden);
//reflow
reflow = this.elements.modal.offsetWidth;
// remove custom dialog class on hide //remove from open dialogs
if (typeof this.__internal.className !== 'undefined' && this.__internal.className !== '') { openDialogs.splice(openDialogs.indexOf(this),1);
removeClass(this.elements.root, this.__internal.className); this.__internal.isOpen = false;
}
// internal on close event ensureNoOverflow();
if(typeof this.hooks.onclose === 'function'){
this.hooks.onclose.call(this);
} }
// allow custom `onclose` method }
dispatchEvent('onclose', this); // last dialog and tab index was set by us, remove it.
if(!openDialogs.length && tabindex === '0'){
//remove from open dialogs document.body.removeAttribute('tabindex');
openDialogs.splice(openDialogs.indexOf(this),1);
this.__internal.isOpen = false;
ensureNoOverflow();
} }
return this; return this;
}, },
...@@ -2504,34 +2591,29 @@ ...@@ -2504,34 +2591,29 @@
* @return {undefined} * @return {undefined}
*/ */
destroy:function(){ destroy:function(){
if (this.__internal.isOpen ) { if(this.__internal) {
//mark dialog for destruction, this will be called on tranistionOut event. if (this.__internal.isOpen ) {
this.__internal.destroy = function(){ //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); destruct(this, initialize);
}; }
//close the dialog to unbind all events.
this.close();
}else{
destruct(this, initialize);
} }
return this; return this;
}, },
}; };
} () ); } () );
var notifier = (function () { var notifier = (function () {
var reflow, var reflow,
element, element,
openInstances = [], openInstances = [],
classes = { classes = defaults.notifier.classes,
base: 'alertify-notifier', baseClass = classes.base;
message: 'ajs-message',
top: 'ajs-top',
right: 'ajs-right',
bottom: 'ajs-bottom',
left: 'ajs-left',
visible: 'ajs-visible',
hidden: 'ajs-hidden'
};
/** /**
* Helper: initializes the notifier instance * Helper: initializes the notifier instance
* *
...@@ -2545,7 +2627,10 @@ ...@@ -2545,7 +2627,10 @@
}; };
element = document.createElement('DIV'); 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); updatePosition(instance);
} }
...@@ -2568,7 +2653,7 @@ ...@@ -2568,7 +2653,7 @@
* *
*/ */
function updatePosition(instance) { function updatePosition(instance) {
element.className = classes.base; element.className = baseClass;
switch (instance.__internal.position) { switch (instance.__internal.position) {
case 'top-right': case 'top-right':
addClass(element, classes.top + ' ' + classes.right); addClass(element, classes.top + ' ' + classes.right);
...@@ -2576,9 +2661,15 @@ ...@@ -2576,9 +2661,15 @@
case 'top-left': case 'top-left':
addClass(element, classes.top + ' ' + classes.left); addClass(element, classes.top + ' ' + classes.left);
break; break;
case 'top-center':
addClass(element, classes.top + ' ' + classes.center);
break;
case 'bottom-left': case 'bottom-left':
addClass(element, classes.bottom + ' ' + classes.left); addClass(element, classes.bottom + ' ' + classes.left);
break; break;
case 'bottom-center':
addClass(element, classes.bottom + ' ' + classes.center);
break;
default: default:
case 'bottom-right': case 'bottom-right':
...@@ -2599,7 +2690,9 @@ ...@@ -2599,7 +2690,9 @@
function create(div, callback) { function create(div, callback) {
function clickDelegate(event, instance) { function clickDelegate(event, instance) {
instance.dismiss(true); if(!instance.__internal.closeButton || event.target.getAttribute('data-close') === 'true'){
instance.dismiss(true);
}
} }
function transitionDone(event, instance) { function transitionDone(event, instance) {
...@@ -2661,6 +2754,7 @@ ...@@ -2661,6 +2754,7 @@
wait = _wait; wait = _wait;
break; break;
} }
this.__internal.closeButton = alertify.defaults.notifier.closeButton;
// set contents // set contents
if (typeof content !== 'undefined') { if (typeof content !== 'undefined') {
this.setContent(content); this.setContent(content);
...@@ -2737,13 +2831,19 @@ ...@@ -2737,13 +2831,19 @@
* *
*/ */
setContent: function (content) { setContent: function (content) {
if (typeof content === 'string') { if (isString(content)) {
clearContents(this.element); clearContents(this.element);
this.element.innerHTML = content; this.element.innerHTML = content;
} else if (content instanceof window.HTMLElement && this.element.firstChild !== content) { } else if (content instanceof window.HTMLElement && this.element.firstChild !== content) {
clearContents(this.element); clearContents(this.element);
this.element.appendChild(content); 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; return this;
}, },
/* /*
...@@ -2814,7 +2914,7 @@ ...@@ -2814,7 +2914,7 @@
initialize(this); initialize(this);
//create new notification message //create new notification message
var div = document.createElement('div'); var div = document.createElement('div');
div.className = classes.message + ((typeof type === 'string' && type !== '') ? ' ajs-' + type : ''); div.className = classes.message + ((typeof type === 'string' && type !== '') ? ' ' + classes.prefix + type : '');
return create(div, callback); return create(div, callback);
}, },
/** /**
...@@ -2834,6 +2934,7 @@ ...@@ -2834,6 +2934,7 @@
} }
}; };
})(); })();
/** /**
* Alertify public API * Alertify public API
* This contains everything that is exposed through the alertify object. * This contains everything that is exposed through the alertify object.
...@@ -3489,7 +3590,7 @@ ...@@ -3489,7 +3590,7 @@
//nothing //nothing
}, },
setMessage: function (message) { setMessage: function (message) {
if (typeof message === 'string') { if (isString(message)) {
clearContents(p); clearContents(p);
p.innerHTML = message; p.innerHTML = message;
} else if (message instanceof window.HTMLElement && p.firstChild !== message) { } else if (message instanceof window.HTMLElement && p.firstChild !== message) {
...@@ -3593,4 +3694,4 @@ ...@@ -3593,4 +3694,4 @@
window.alertify = alertify; window.alertify = alertify;
} }
} ( typeof window !== 'undefined' ? window : this ) ); } ( typeof window !== 'undefined' ? window : this ) );
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment