You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3609 lines
137 KiB
3609 lines
137 KiB
/**
|
|
* alertifyjs 1.11.4 http://alertifyjs.com
|
|
* AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
|
|
* Copyright 2019 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
|
|
* Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
|
|
( function ( window ) {
|
|
'use strict';
|
|
|
|
/**
|
|
* Keys enum
|
|
* @type {Object}
|
|
*/
|
|
var keys = {
|
|
ENTER: 13,
|
|
ESC: 27,
|
|
F1: 112,
|
|
F12: 123,
|
|
LEFT: 37,
|
|
RIGHT: 39
|
|
};
|
|
/**
|
|
* Default options
|
|
* @type {Object}
|
|
*/
|
|
var defaults = {
|
|
autoReset:true,
|
|
basic:false,
|
|
closable:true,
|
|
closableByDimmer:true,
|
|
frameless:false,
|
|
maintainFocus:true, //global default not per instance, applies to all dialogs
|
|
maximizable:true,
|
|
modal:true,
|
|
movable:true,
|
|
moveBounded:false,
|
|
overflow:true,
|
|
padding: true,
|
|
pinnable:true,
|
|
pinned:true,
|
|
preventBodyShift:false, //global default not per instance, applies to all dialogs
|
|
resizable:true,
|
|
startMaximized:false,
|
|
transition:'pulse',
|
|
notifier:{
|
|
delay:5,
|
|
position:'bottom-right',
|
|
closeButton:false
|
|
},
|
|
glossary:{
|
|
title:'AlertifyJS',
|
|
ok: 'OK',
|
|
cancel: 'Cancel',
|
|
acccpt: 'Accept',
|
|
deny: 'Deny',
|
|
confirm: 'Confirm',
|
|
decline: 'Decline',
|
|
close: 'Close',
|
|
maximize: 'Maximize',
|
|
restore: 'Restore',
|
|
},
|
|
theme:{
|
|
input:'ajs-input',
|
|
ok:'ajs-ok',
|
|
cancel:'ajs-cancel',
|
|
}
|
|
};
|
|
|
|
//holds open dialogs instances
|
|
var openDialogs = [];
|
|
|
|
/**
|
|
* [Helper] Adds the specified class(es) to the element.
|
|
*
|
|
* @element {node} The element
|
|
* @className {string} One or more space-separated classes to be added to the class attribute of the element.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function addClass(element,classNames){
|
|
element.className += ' ' + classNames;
|
|
}
|
|
|
|
/**
|
|
* [Helper] Removes the specified class(es) from the element.
|
|
*
|
|
* @element {node} The element
|
|
* @className {string} One or more space-separated classes to be removed from the class attribute of the element.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function removeClass(element, classNames) {
|
|
var original = element.className.split(' ');
|
|
var toBeRemoved = classNames.split(' ');
|
|
for (var x = 0; x < toBeRemoved.length; x += 1) {
|
|
var index = original.indexOf(toBeRemoved[x]);
|
|
if (index > -1){
|
|
original.splice(index,1);
|
|
}
|
|
}
|
|
element.className = original.join(' ');
|
|
}
|
|
|
|
/**
|
|
* [Helper] Checks if the document is RTL
|
|
*
|
|
* @return {Boolean} True if the document is RTL, false otherwise.
|
|
*/
|
|
function isRightToLeft(){
|
|
return window.getComputedStyle(document.body).direction === 'rtl';
|
|
}
|
|
/**
|
|
* [Helper] Get the document current scrollTop
|
|
*
|
|
* @return {Number} current document scrollTop value
|
|
*/
|
|
function getScrollTop(){
|
|
return ((document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop);
|
|
}
|
|
|
|
/**
|
|
* [Helper] Get the document current scrollLeft
|
|
*
|
|
* @return {Number} current document scrollLeft value
|
|
*/
|
|
function getScrollLeft(){
|
|
return ((document.documentElement && document.documentElement.scrollLeft) || document.body.scrollLeft);
|
|
}
|
|
|
|
/**
|
|
* Helper: clear contents
|
|
*
|
|
*/
|
|
function clearContents(element){
|
|
while (element.lastChild) {
|
|
element.removeChild(element.lastChild);
|
|
}
|
|
}
|
|
/**
|
|
* Extends a given prototype by merging properties from base into sub.
|
|
*
|
|
* @sub {Object} sub The prototype being overwritten.
|
|
* @base {Object} base The prototype being written.
|
|
*
|
|
* @return {Object} The extended prototype.
|
|
*/
|
|
function copy(src) {
|
|
if(null === src){
|
|
return src;
|
|
}
|
|
var cpy;
|
|
if(Array.isArray(src)){
|
|
cpy = [];
|
|
for(var x=0;x<src.length;x+=1){
|
|
cpy.push(copy(src[x]));
|
|
}
|
|
return cpy;
|
|
}
|
|
|
|
if(src instanceof Date){
|
|
return new Date(src.getTime());
|
|
}
|
|
|
|
if(src instanceof RegExp){
|
|
cpy = new RegExp(src.source);
|
|
cpy.global = src.global;
|
|
cpy.ignoreCase = src.ignoreCase;
|
|
cpy.multiline = src.multiline;
|
|
cpy.lastIndex = src.lastIndex;
|
|
return cpy;
|
|
}
|
|
|
|
if(typeof src === 'object'){
|
|
cpy = {};
|
|
// copy dialog pototype over definition.
|
|
for (var prop in src) {
|
|
if (src.hasOwnProperty(prop)) {
|
|
cpy[prop] = copy(src[prop]);
|
|
}
|
|
}
|
|
return cpy;
|
|
}
|
|
return src;
|
|
}
|
|
/**
|
|
* Helper: destruct the dialog
|
|
*
|
|
*/
|
|
function destruct(instance, initialize){
|
|
if(instance.elements){
|
|
//delete the dom and it's references.
|
|
var root = instance.elements.root;
|
|
root.parentNode.removeChild(root);
|
|
delete instance.elements;
|
|
//copy back initial settings.
|
|
instance.settings = copy(instance.__settings);
|
|
//re-reference init function.
|
|
instance.__init = initialize;
|
|
//delete __internal variable to allow re-initialization.
|
|
delete instance.__internal;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use a closure to return proper event listener method. Try to use
|
|
* `addEventListener` by default but fallback to `attachEvent` for
|
|
* unsupported browser. The closure simply ensures that the test doesn't
|
|
* happen every time the method is called.
|
|
*
|
|
* @param {Node} el Node element
|
|
* @param {String} event Event type
|
|
* @param {Function} fn Callback of event
|
|
* @return {Function}
|
|
*/
|
|
var on = (function () {
|
|
if (document.addEventListener) {
|
|
return function (el, event, fn, useCapture) {
|
|
el.addEventListener(event, fn, useCapture === true);
|
|
};
|
|
} else if (document.attachEvent) {
|
|
return function (el, event, fn) {
|
|
el.attachEvent('on' + event, fn);
|
|
};
|
|
}
|
|
}());
|
|
|
|
/**
|
|
* Use a closure to return proper event listener method. Try to use
|
|
* `removeEventListener` by default but fallback to `detachEvent` for
|
|
* unsupported browser. The closure simply ensures that the test doesn't
|
|
* happen every time the method is called.
|
|
*
|
|
* @param {Node} el Node element
|
|
* @param {String} event Event type
|
|
* @param {Function} fn Callback of event
|
|
* @return {Function}
|
|
*/
|
|
var off = (function () {
|
|
if (document.removeEventListener) {
|
|
return function (el, event, fn, useCapture) {
|
|
el.removeEventListener(event, fn, useCapture === true);
|
|
};
|
|
} else if (document.detachEvent) {
|
|
return function (el, event, fn) {
|
|
el.detachEvent('on' + event, fn);
|
|
};
|
|
}
|
|
}());
|
|
|
|
/**
|
|
* Prevent default event from firing
|
|
*
|
|
* @param {Event} event Event object
|
|
* @return {undefined}
|
|
|
|
function prevent ( event ) {
|
|
if ( event ) {
|
|
if ( event.preventDefault ) {
|
|
event.preventDefault();
|
|
} else {
|
|
event.returnValue = false;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
var transition = (function () {
|
|
var t, type;
|
|
var supported = false;
|
|
var transitions = {
|
|
'animation' : 'animationend',
|
|
'OAnimation' : 'oAnimationEnd oanimationend',
|
|
'msAnimation' : 'MSAnimationEnd',
|
|
'MozAnimation' : 'animationend',
|
|
'WebkitAnimation' : 'webkitAnimationEnd'
|
|
};
|
|
|
|
for (t in transitions) {
|
|
if (document.documentElement.style[t] !== undefined) {
|
|
type = transitions[t];
|
|
supported = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: type,
|
|
supported: supported
|
|
};
|
|
}());
|
|
|
|
/**
|
|
* Creates event handler delegate that sends the instance as last argument.
|
|
*
|
|
* @return {Function} a function wrapper which sends the instance as last argument.
|
|
*/
|
|
function delegate(context, method) {
|
|
return function () {
|
|
if (arguments.length > 0) {
|
|
var args = [];
|
|
for (var x = 0; x < arguments.length; x += 1) {
|
|
args.push(arguments[x]);
|
|
}
|
|
args.push(context);
|
|
return method.apply(context, args);
|
|
}
|
|
return method.apply(context, [null, context]);
|
|
};
|
|
}
|
|
/**
|
|
* Helper for creating a dialog close event.
|
|
*
|
|
* @return {object}
|
|
*/
|
|
function createCloseEvent(index, button) {
|
|
return {
|
|
index: index,
|
|
button: button,
|
|
cancel: false
|
|
};
|
|
}
|
|
/**
|
|
* Helper for dispatching events.
|
|
*
|
|
* @param {string} evenType The type of the event to disptach.
|
|
* @param {object} instance The dialog instance disptaching the event.
|
|
*
|
|
* @return {any} The result of the invoked function.
|
|
*/
|
|
function dispatchEvent(eventType, instance) {
|
|
if ( typeof instance.get(eventType) === 'function' ) {
|
|
return instance.get(eventType).call(instance);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Super class for all dialogs
|
|
*
|
|
* @return {Object} base dialog prototype
|
|
*/
|
|
var dialog = (function () {
|
|
var //holds the list of used keys.
|
|
usedKeys = [],
|
|
//dummy variable, used to trigger dom reflow.
|
|
reflow = null,
|
|
//holds body tab index in case it has any.
|
|
tabindex = false,
|
|
//condition for detecting safari
|
|
isSafari = window.navigator.userAgent.indexOf('Safari') > -1 && window.navigator.userAgent.indexOf('Chrome') < 0,
|
|
//dialog building blocks
|
|
templates = {
|
|
dimmer:'<div class="ajs-dimmer"></div>',
|
|
/*tab index required to fire click event before body focus*/
|
|
modal: '<div class="ajs-modal" tabindex="0"></div>',
|
|
dialog: '<div class="ajs-dialog" tabindex="0"></div>',
|
|
reset: '<button class="ajs-reset"></button>',
|
|
commands: '<div class="ajs-commands"><button class="ajs-pin"></button><button class="ajs-maximize"></button><button class="ajs-close"></button></div>',
|
|
header: '<div class="ajs-header"></div>',
|
|
body: '<div class="ajs-body"></div>',
|
|
content: '<div class="ajs-content"></div>',
|
|
footer: '<div class="ajs-footer"></div>',
|
|
buttons: { primary: '<div class="ajs-primary ajs-buttons"></div>', auxiliary: '<div class="ajs-auxiliary ajs-buttons"></div>' },
|
|
button: '<button class="ajs-button"></button>',
|
|
resizeHandle: '<div class="ajs-handle"></div>',
|
|
},
|
|
//common class names
|
|
classes = {
|
|
animationIn: 'ajs-in',
|
|
animationOut: 'ajs-out',
|
|
base: 'alertify',
|
|
basic:'ajs-basic',
|
|
capture: 'ajs-capture',
|
|
closable:'ajs-closable',
|
|
fixed: 'ajs-fixed',
|
|
frameless:'ajs-frameless',
|
|
hidden: 'ajs-hidden',
|
|
maximize: 'ajs-maximize',
|
|
maximized: 'ajs-maximized',
|
|
maximizable:'ajs-maximizable',
|
|
modeless: 'ajs-modeless',
|
|
movable: 'ajs-movable',
|
|
noSelection: 'ajs-no-selection',
|
|
noOverflow: 'ajs-no-overflow',
|
|
noPadding:'ajs-no-padding',
|
|
pin:'ajs-pin',
|
|
pinnable:'ajs-pinnable',
|
|
prefix: 'ajs-',
|
|
resizable: 'ajs-resizable',
|
|
restore: 'ajs-restore',
|
|
shake:'ajs-shake',
|
|
unpinned:'ajs-unpinned',
|
|
};
|
|
|
|
/**
|
|
* Helper: initializes the dialog instance
|
|
*
|
|
* @return {Number} The total count of currently open modals.
|
|
*/
|
|
function initialize(instance){
|
|
|
|
if(!instance.__internal){
|
|
|
|
//no need to expose init after this.
|
|
delete instance.__init;
|
|
|
|
//keep a copy of initial dialog settings
|
|
if(!instance.__settings){
|
|
instance.__settings = copy(instance.settings);
|
|
}
|
|
|
|
//get dialog buttons/focus setup
|
|
var setup;
|
|
if(typeof instance.setup === 'function'){
|
|
setup = instance.setup();
|
|
setup.options = setup.options || {};
|
|
setup.focus = setup.focus || {};
|
|
}else{
|
|
setup = {
|
|
buttons:[],
|
|
focus:{
|
|
element:null,
|
|
select:false
|
|
},
|
|
options:{
|
|
}
|
|
};
|
|
}
|
|
|
|
//initialize hooks object.
|
|
if(typeof instance.hooks !== 'object'){
|
|
instance.hooks = {};
|
|
}
|
|
|
|
//copy buttons defintion
|
|
var buttonsDefinition = [];
|
|
if(Array.isArray(setup.buttons)){
|
|
for(var b=0;b<setup.buttons.length;b+=1){
|
|
var ref = setup.buttons[b],
|
|
cpy = {};
|
|
for (var i in ref) {
|
|
if (ref.hasOwnProperty(i)) {
|
|
cpy[i] = ref[i];
|
|
}
|
|
}
|
|
buttonsDefinition.push(cpy);
|
|
}
|
|
}
|
|
|
|
var internal = instance.__internal = {
|
|
/**
|
|
* Flag holding the open state of the dialog
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
isOpen:false,
|
|
/**
|
|
* Active element is the element that will receive focus after
|
|
* closing the dialog. It defaults as the body tag, but gets updated
|
|
* to the last focused element before the dialog was opened.
|
|
*
|
|
* @type {Node}
|
|
*/
|
|
activeElement:document.body,
|
|
timerIn:undefined,
|
|
timerOut:undefined,
|
|
buttons: buttonsDefinition,
|
|
focus: setup.focus,
|
|
options: {
|
|
title: undefined,
|
|
modal: undefined,
|
|
basic:undefined,
|
|
frameless:undefined,
|
|
pinned: undefined,
|
|
movable: undefined,
|
|
moveBounded:undefined,
|
|
resizable: undefined,
|
|
autoReset: undefined,
|
|
closable: undefined,
|
|
closableByDimmer: undefined,
|
|
maximizable: undefined,
|
|
startMaximized: undefined,
|
|
pinnable: undefined,
|
|
transition: undefined,
|
|
padding:undefined,
|
|
overflow:undefined,
|
|
onshow:undefined,
|
|
onclosing:undefined,
|
|
onclose:undefined,
|
|
onfocus:undefined,
|
|
onmove:undefined,
|
|
onmoved:undefined,
|
|
onresize:undefined,
|
|
onresized:undefined,
|
|
onmaximize:undefined,
|
|
onmaximized:undefined,
|
|
onrestore:undefined,
|
|
onrestored:undefined
|
|
},
|
|
resetHandler:undefined,
|
|
beginMoveHandler:undefined,
|
|
beginResizeHandler:undefined,
|
|
bringToFrontHandler:undefined,
|
|
modalClickHandler:undefined,
|
|
buttonsClickHandler:undefined,
|
|
commandsClickHandler:undefined,
|
|
transitionInHandler:undefined,
|
|
transitionOutHandler:undefined,
|
|
destroy:undefined
|
|
};
|
|
|
|
var elements = {};
|
|
//root node
|
|
elements.root = document.createElement('div');
|
|
//prevent FOUC in case of async styles loading.
|
|
elements.root.style.display = 'none';
|
|
elements.root.className = classes.base + ' ' + classes.hidden + ' ';
|
|
|
|
elements.root.innerHTML = templates.dimmer + templates.modal;
|
|
|
|
//dimmer
|
|
elements.dimmer = elements.root.firstChild;
|
|
|
|
//dialog
|
|
elements.modal = elements.root.lastChild;
|
|
elements.modal.innerHTML = templates.dialog;
|
|
elements.dialog = elements.modal.firstChild;
|
|
elements.dialog.innerHTML = templates.reset + templates.commands + templates.header + templates.body + templates.footer + templates.resizeHandle + templates.reset;
|
|
|
|
//reset links
|
|
elements.reset = [];
|
|
elements.reset.push(elements.dialog.firstChild);
|
|
elements.reset.push(elements.dialog.lastChild);
|
|
|
|
//commands
|
|
elements.commands = {};
|
|
elements.commands.container = elements.reset[0].nextSibling;
|
|
elements.commands.pin = elements.commands.container.firstChild;
|
|
elements.commands.maximize = elements.commands.pin.nextSibling;
|
|
elements.commands.close = elements.commands.maximize.nextSibling;
|
|
|
|
//header
|
|
elements.header = elements.commands.container.nextSibling;
|
|
|
|
//body
|
|
elements.body = elements.header.nextSibling;
|
|
elements.body.innerHTML = templates.content;
|
|
elements.content = elements.body.firstChild;
|
|
|
|
//footer
|
|
elements.footer = elements.body.nextSibling;
|
|
elements.footer.innerHTML = templates.buttons.auxiliary + templates.buttons.primary;
|
|
|
|
//resize handle
|
|
elements.resizeHandle = elements.footer.nextSibling;
|
|
|
|
//buttons
|
|
elements.buttons = {};
|
|
elements.buttons.auxiliary = elements.footer.firstChild;
|
|
elements.buttons.primary = elements.buttons.auxiliary.nextSibling;
|
|
elements.buttons.primary.innerHTML = templates.button;
|
|
elements.buttonTemplate = elements.buttons.primary.firstChild;
|
|
//remove button template
|
|
elements.buttons.primary.removeChild(elements.buttonTemplate);
|
|
|
|
for(var x=0; x < instance.__internal.buttons.length; x+=1) {
|
|
var button = instance.__internal.buttons[x];
|
|
|
|
// add to the list of used keys.
|
|
if(usedKeys.indexOf(button.key) < 0){
|
|
usedKeys.push(button.key);
|
|
}
|
|
|
|
button.element = elements.buttonTemplate.cloneNode();
|
|
button.element.innerHTML = button.text;
|
|
if(typeof button.className === 'string' && button.className !== ''){
|
|
addClass(button.element, button.className);
|
|
}
|
|
for(var key in button.attrs){
|
|
if(key !== 'className' && button.attrs.hasOwnProperty(key)){
|
|
button.element.setAttribute(key, button.attrs[key]);
|
|
}
|
|
}
|
|
if(button.scope === 'auxiliary'){
|
|
elements.buttons.auxiliary.appendChild(button.element);
|
|
}else{
|
|
elements.buttons.primary.appendChild(button.element);
|
|
}
|
|
}
|
|
//make elements pubic
|
|
instance.elements = elements;
|
|
|
|
//save event handlers delegates
|
|
internal.resetHandler = delegate(instance, onReset);
|
|
internal.beginMoveHandler = delegate(instance, beginMove);
|
|
internal.beginResizeHandler = delegate(instance, beginResize);
|
|
internal.bringToFrontHandler = delegate(instance, bringToFront);
|
|
internal.modalClickHandler = delegate(instance, modalClickHandler);
|
|
internal.buttonsClickHandler = delegate(instance, buttonsClickHandler);
|
|
internal.commandsClickHandler = delegate(instance, commandsClickHandler);
|
|
internal.transitionInHandler = delegate(instance, handleTransitionInEvent);
|
|
internal.transitionOutHandler = delegate(instance, handleTransitionOutEvent);
|
|
|
|
//settings
|
|
for(var opKey in internal.options){
|
|
if(setup.options[opKey] !== undefined){
|
|
// if found in user options
|
|
instance.set(opKey, setup.options[opKey]);
|
|
}else if(alertify.defaults.hasOwnProperty(opKey)) {
|
|
// else if found in defaults options
|
|
instance.set(opKey, alertify.defaults[opKey]);
|
|
}else if(opKey === 'title' ) {
|
|
// else if title key, use alertify.defaults.glossary
|
|
instance.set(opKey, alertify.defaults.glossary[opKey]);
|
|
}
|
|
}
|
|
|
|
// allow dom customization
|
|
if(typeof instance.build === 'function'){
|
|
instance.build();
|
|
}
|
|
}
|
|
|
|
//add to the end of the DOM tree.
|
|
document.body.appendChild(instance.elements.root);
|
|
}
|
|
|
|
/**
|
|
* Helper: maintains scroll position
|
|
*
|
|
*/
|
|
var scrollX, scrollY;
|
|
function saveScrollPosition(){
|
|
scrollX = getScrollLeft();
|
|
scrollY = getScrollTop();
|
|
}
|
|
function restoreScrollPosition(){
|
|
window.scrollTo(scrollX, scrollY);
|
|
}
|
|
|
|
/**
|
|
* Helper: adds/removes no-overflow class from body
|
|
*
|
|
*/
|
|
function ensureNoOverflow(){
|
|
var requiresNoOverflow = 0;
|
|
for(var x=0;x<openDialogs.length;x+=1){
|
|
var instance = openDialogs[x];
|
|
if(instance.isModal() || instance.isMaximized()){
|
|
requiresNoOverflow+=1;
|
|
}
|
|
}
|
|
if(requiresNoOverflow === 0 && document.body.className.indexOf(classes.noOverflow) >= 0){
|
|
//last open modal or last maximized one
|
|
removeClass(document.body, classes.noOverflow);
|
|
preventBodyShift(false);
|
|
}else if(requiresNoOverflow > 0 && document.body.className.indexOf(classes.noOverflow) < 0){
|
|
//first open modal or first maximized one
|
|
preventBodyShift(true);
|
|
addClass(document.body, classes.noOverflow);
|
|
}
|
|
}
|
|
var top = '', topScroll = 0;
|
|
/**
|
|
* Helper: prevents body shift.
|
|
*
|
|
*/
|
|
function preventBodyShift(add){
|
|
if(alertify.defaults.preventBodyShift){
|
|
if(add && document.documentElement.scrollHeight > document.documentElement.clientHeight ){//&& openDialogs[openDialogs.length-1].elements.dialog.clientHeight <= document.documentElement.clientHeight){
|
|
topScroll = scrollY;
|
|
top = window.getComputedStyle(document.body).top;
|
|
addClass(document.body, classes.fixed);
|
|
document.body.style.top = -scrollY + 'px';
|
|
} else if(!add) {
|
|
scrollY = topScroll;
|
|
document.body.style.top = top;
|
|
removeClass(document.body, classes.fixed);
|
|
restoreScrollPosition();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the name of the transition used to show/hide the dialog
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
*/
|
|
function updateTransition(instance, value, oldValue){
|
|
if(typeof oldValue === 'string'){
|
|
removeClass(instance.elements.root,classes.prefix + oldValue);
|
|
}
|
|
addClass(instance.elements.root, classes.prefix + value);
|
|
reflow = instance.elements.root.offsetWidth;
|
|
}
|
|
|
|
/**
|
|
* Toggles the dialog display mode
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function updateDisplayMode(instance){
|
|
if(instance.get('modal')){
|
|
|
|
//make modal
|
|
removeClass(instance.elements.root, classes.modeless);
|
|
|
|
//only if open
|
|
if(instance.isOpen()){
|
|
unbindModelessEvents(instance);
|
|
|
|
//in case a pinned modless dialog was made modal while open.
|
|
updateAbsPositionFix(instance);
|
|
|
|
ensureNoOverflow();
|
|
}
|
|
}else{
|
|
//make modelss
|
|
addClass(instance.elements.root, classes.modeless);
|
|
|
|
//only if open
|
|
if(instance.isOpen()){
|
|
bindModelessEvents(instance);
|
|
|
|
//in case pin/unpin was called while a modal is open
|
|
updateAbsPositionFix(instance);
|
|
|
|
ensureNoOverflow();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggles the dialog basic view mode
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function updateBasicMode(instance){
|
|
if (instance.get('basic')) {
|
|
// add class
|
|
addClass(instance.elements.root, classes.basic);
|
|
} else {
|
|
// remove class
|
|
removeClass(instance.elements.root, classes.basic);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggles the dialog frameless view mode
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function updateFramelessMode(instance){
|
|
if (instance.get('frameless')) {
|
|
// add class
|
|
addClass(instance.elements.root, classes.frameless);
|
|
} else {
|
|
// remove class
|
|
removeClass(instance.elements.root, classes.frameless);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper: Brings the modeless dialog to front, attached to modeless dialogs.
|
|
*
|
|
* @param {Event} event Focus event
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function bringToFront(event, instance){
|
|
|
|
// Do not bring to front if preceeded by an open modal
|
|
var index = openDialogs.indexOf(instance);
|
|
for(var x=index+1;x<openDialogs.length;x+=1){
|
|
if(openDialogs[x].isModal()){
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Bring to front by making it the last child.
|
|
if(document.body.lastChild !== instance.elements.root){
|
|
document.body.appendChild(instance.elements.root);
|
|
//also make sure its at the end of the list
|
|
openDialogs.splice(openDialogs.indexOf(instance),1);
|
|
openDialogs.push(instance);
|
|
setFocus(instance);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Helper: reflects dialogs options updates
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
* @param {String} option The updated option name.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function optionUpdated(instance, option, oldValue, newValue){
|
|
switch(option){
|
|
case 'title':
|
|
instance.setHeader(newValue);
|
|
break;
|
|
case 'modal':
|
|
updateDisplayMode(instance);
|
|
break;
|
|
case 'basic':
|
|
updateBasicMode(instance);
|
|
break;
|
|
case 'frameless':
|
|
updateFramelessMode(instance);
|
|
break;
|
|
case 'pinned':
|
|
updatePinned(instance);
|
|
break;
|
|
case 'closable':
|
|
updateClosable(instance);
|
|
break;
|
|
case 'maximizable':
|
|
updateMaximizable(instance);
|
|
break;
|
|
case 'pinnable':
|
|
updatePinnable(instance);
|
|
break;
|
|
case 'movable':
|
|
updateMovable(instance);
|
|
break;
|
|
case 'resizable':
|
|
updateResizable(instance);
|
|
break;
|
|
case 'padding':
|
|
if(newValue){
|
|
removeClass(instance.elements.root, classes.noPadding);
|
|
}else if(instance.elements.root.className.indexOf(classes.noPadding) < 0){
|
|
addClass(instance.elements.root, classes.noPadding);
|
|
}
|
|
break;
|
|
case 'overflow':
|
|
if(newValue){
|
|
removeClass(instance.elements.root, classes.noOverflow);
|
|
}else if(instance.elements.root.className.indexOf(classes.noOverflow) < 0){
|
|
addClass(instance.elements.root, classes.noOverflow);
|
|
}
|
|
break;
|
|
case 'transition':
|
|
updateTransition(instance,newValue, oldValue);
|
|
break;
|
|
}
|
|
|
|
// internal on option updated event
|
|
if(typeof instance.hooks.onupdate === 'function'){
|
|
instance.hooks.onupdate.call(instance, option, oldValue, newValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper: reflects dialogs options updates
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
* @param {Object} obj The object to set/get a value on/from.
|
|
* @param {Function} callback The callback function to call if the key was found.
|
|
* @param {String|Object} key A string specifying a propery name or a collection of key value pairs.
|
|
* @param {Object} value Optional, the value associated with the key (in case it was a string).
|
|
* @param {String} option The updated option name.
|
|
*
|
|
* @return {Object} result object
|
|
* The result objects has an 'op' property, indicating of this is a SET or GET operation.
|
|
* GET:
|
|
* - found: a flag indicating if the key was found or not.
|
|
* - value: the property value.
|
|
* SET:
|
|
* - items: a list of key value pairs of the properties being set.
|
|
* each contains:
|
|
* - found: a flag indicating if the key was found or not.
|
|
* - key: the property key.
|
|
* - value: the property value.
|
|
*/
|
|
function update(instance, obj, callback, key, value){
|
|
var result = {op:undefined, items: [] };
|
|
if(typeof value === 'undefined' && typeof key === 'string') {
|
|
//get
|
|
result.op = 'get';
|
|
if(obj.hasOwnProperty(key)){
|
|
result.found = true;
|
|
result.value = obj[key];
|
|
}else{
|
|
result.found = false;
|
|
result.value = undefined;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var old;
|
|
//set
|
|
result.op = 'set';
|
|
if(typeof key === 'object'){
|
|
//set multiple
|
|
var args = key;
|
|
for (var prop in args) {
|
|
if (obj.hasOwnProperty(prop)) {
|
|
if(obj[prop] !== args[prop]){
|
|
old = obj[prop];
|
|
obj[prop] = args[prop];
|
|
callback.call(instance,prop, old, args[prop]);
|
|
}
|
|
result.items.push({ 'key': prop, 'value': args[prop], 'found':true});
|
|
}else{
|
|
result.items.push({ 'key': prop, 'value': args[prop], 'found':false});
|
|
}
|
|
}
|
|
} else if (typeof key === 'string'){
|
|
//set single
|
|
if (obj.hasOwnProperty(key)) {
|
|
if(obj[key] !== value){
|
|
old = obj[key];
|
|
obj[key] = value;
|
|
callback.call(instance,key, old, value);
|
|
}
|
|
result.items.push({'key': key, 'value': value , 'found':true});
|
|
|
|
}else{
|
|
result.items.push({'key': key, 'value': value , 'found':false});
|
|
}
|
|
} else {
|
|
//invalid params
|
|
throw new Error('args must be a string or object');
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggers a close event.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function triggerClose(instance) {
|
|
var found;
|
|
triggerCallback(instance, function (button) {
|
|
return found = (button.invokeOnClose === true);
|
|
});
|
|
//none of the buttons registered as onclose callback
|
|
//close the dialog
|
|
if (!found && instance.isOpen()) {
|
|
instance.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dialogs commands event handler, attached to the dialog commands element.
|
|
*
|
|
* @param {Event} event DOM event object.
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function commandsClickHandler(event, instance) {
|
|
var target = event.srcElement || event.target;
|
|
switch (target) {
|
|
case instance.elements.commands.pin:
|
|
if (!instance.isPinned()) {
|
|
pin(instance);
|
|
} else {
|
|
unpin(instance);
|
|
}
|
|
break;
|
|
case instance.elements.commands.maximize:
|
|
if (!instance.isMaximized()) {
|
|
maximize(instance);
|
|
} else {
|
|
restore(instance);
|
|
}
|
|
break;
|
|
case instance.elements.commands.close:
|
|
triggerClose(instance);
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Helper: pins the modeless dialog.
|
|
*
|
|
* @param {Object} instance The dialog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function pin(instance) {
|
|
//pin the dialog
|
|
instance.set('pinned', true);
|
|
}
|
|
|
|
/**
|
|
* Helper: unpins the modeless dialog.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function unpin(instance) {
|
|
//unpin the dialog
|
|
instance.set('pinned', false);
|
|
}
|
|
|
|
|
|
/**
|
|
* Helper: enlarges the dialog to fill the entire screen.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function maximize(instance) {
|
|
// allow custom `onmaximize` method
|
|
dispatchEvent('onmaximize', instance);
|
|
//maximize the dialog
|
|
addClass(instance.elements.root, classes.maximized);
|
|
if (instance.isOpen()) {
|
|
ensureNoOverflow();
|
|
}
|
|
// allow custom `onmaximized` method
|
|
dispatchEvent('onmaximized', instance);
|
|
}
|
|
|
|
/**
|
|
* Helper: returns the dialog to its former size.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function restore(instance) {
|
|
// allow custom `onrestore` method
|
|
dispatchEvent('onrestore', instance);
|
|
//maximize the dialog
|
|
removeClass(instance.elements.root, classes.maximized);
|
|
if (instance.isOpen()) {
|
|
ensureNoOverflow();
|
|
}
|
|
// allow custom `onrestored` method
|
|
dispatchEvent('onrestored', instance);
|
|
}
|
|
|
|
/**
|
|
* Show or hide the maximize box.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
* @param {Boolean} on True to add the behavior, removes it otherwise.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function updatePinnable(instance) {
|
|
if (instance.get('pinnable')) {
|
|
// add class
|
|
addClass(instance.elements.root, classes.pinnable);
|
|
} else {
|
|
// remove class
|
|
removeClass(instance.elements.root, classes.pinnable);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper: Fixes the absolutly positioned modal div position.
|
|
*
|
|
* @param {Object} instance The dialog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function addAbsPositionFix(instance) {
|
|
var scrollLeft = getScrollLeft();
|
|
instance.elements.modal.style.marginTop = getScrollTop() + 'px';
|
|
instance.elements.modal.style.marginLeft = scrollLeft + 'px';
|
|
instance.elements.modal.style.marginRight = (-scrollLeft) + 'px';
|
|
}
|
|
|
|
/**
|
|
* Helper: Removes the absolutly positioned modal div position fix.
|
|
*
|
|
* @param {Object} instance The dialog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function removeAbsPositionFix(instance) {
|
|
var marginTop = parseInt(instance.elements.modal.style.marginTop, 10);
|
|
var marginLeft = parseInt(instance.elements.modal.style.marginLeft, 10);
|
|
instance.elements.modal.style.marginTop = '';
|
|
instance.elements.modal.style.marginLeft = '';
|
|
instance.elements.modal.style.marginRight = '';
|
|
|
|
if (instance.isOpen()) {
|
|
var top = 0,
|
|
left = 0
|
|
;
|
|
if (instance.elements.dialog.style.top !== '') {
|
|
top = parseInt(instance.elements.dialog.style.top, 10);
|
|
}
|
|
instance.elements.dialog.style.top = (top + (marginTop - getScrollTop())) + 'px';
|
|
|
|
if (instance.elements.dialog.style.left !== '') {
|
|
left = parseInt(instance.elements.dialog.style.left, 10);
|
|
}
|
|
instance.elements.dialog.style.left = (left + (marginLeft - getScrollLeft())) + 'px';
|
|
}
|
|
}
|
|
/**
|
|
* Helper: Adds/Removes the absolutly positioned modal div position fix based on its pinned setting.
|
|
*
|
|
* @param {Object} instance The dialog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function updateAbsPositionFix(instance) {
|
|
// if modeless and unpinned add fix
|
|
if (!instance.get('modal') && !instance.get('pinned')) {
|
|
addAbsPositionFix(instance);
|
|
} else {
|
|
removeAbsPositionFix(instance);
|
|
}
|
|
}
|
|
/**
|
|
* Toggles the dialog position lock | modeless only.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
* @param {Boolean} on True to make it modal, false otherwise.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function updatePinned(instance) {
|
|
if (instance.get('pinned')) {
|
|
removeClass(instance.elements.root, classes.unpinned);
|
|
if (instance.isOpen()) {
|
|
removeAbsPositionFix(instance);
|
|
}
|
|
} else {
|
|
addClass(instance.elements.root, classes.unpinned);
|
|
if (instance.isOpen() && !instance.isModal()) {
|
|
addAbsPositionFix(instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show or hide the maximize box.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
* @param {Boolean} on True to add the behavior, removes it otherwise.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function updateMaximizable(instance) {
|
|
if (instance.get('maximizable')) {
|
|
// add class
|
|
addClass(instance.elements.root, classes.maximizable);
|
|
} else {
|
|
// remove class
|
|
removeClass(instance.elements.root, classes.maximizable);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show or hide the close box.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
* @param {Boolean} on True to add the behavior, removes it otherwise.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function updateClosable(instance) {
|
|
if (instance.get('closable')) {
|
|
// add class
|
|
addClass(instance.elements.root, classes.closable);
|
|
bindClosableEvents(instance);
|
|
} else {
|
|
// remove class
|
|
removeClass(instance.elements.root, classes.closable);
|
|
unbindClosableEvents(instance);
|
|
}
|
|
}
|
|
|
|
|
|
var cancelClick = false,// flag to cancel click event if already handled by end resize event (the mousedown, mousemove, mouseup sequence fires a click event.).
|
|
modalClickHandlerTS=0 // stores last click timestamp to prevent executing the handler twice on double click.
|
|
;
|
|
|
|
/**
|
|
* Helper: closes the modal dialog when clicking the modal
|
|
*
|
|
* @param {Event} event DOM event object.
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function modalClickHandler(event, instance) {
|
|
if(event.timeStamp - modalClickHandlerTS > 200 && (modalClickHandlerTS = event.timeStamp) && !cancelClick){
|
|
var target = event.srcElement || event.target;
|
|
if (instance.get('closableByDimmer') === true && target === instance.elements.modal) {
|
|
triggerClose(instance);
|
|
}
|
|
cancelClick = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// stores last call timestamp to prevent triggering the callback twice.
|
|
var callbackTS = 0;
|
|
// flag to cancel keyup event if already handled by click event (pressing Enter on a focusted button).
|
|
var cancelKeyup = false;
|
|
/**
|
|
* Helper: triggers a button callback
|
|
*
|
|
* @param {Object} The dilog instance.
|
|
* @param {Function} Callback to check which button triggered the event.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function triggerCallback(instance, check) {
|
|
if(Date.now() - callbackTS > 200 && (callbackTS = Date.now())){
|
|
for (var idx = 0; idx < instance.__internal.buttons.length; idx += 1) {
|
|
var button = instance.__internal.buttons[idx];
|
|
if (!button.element.disabled && check(button)) {
|
|
var closeEvent = createCloseEvent(idx, button);
|
|
if (typeof instance.callback === 'function') {
|
|
instance.callback.apply(instance, [closeEvent]);
|
|
}
|
|
//close the dialog only if not canceled.
|
|
if (closeEvent.cancel === false) {
|
|
instance.close();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clicks event handler, attached to the dialog footer.
|
|
*
|
|
* @param {Event} DOM event object.
|
|
* @param {Object} The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function buttonsClickHandler(event, instance) {
|
|
var target = event.srcElement || event.target;
|
|
triggerCallback(instance, function (button) {
|
|
// if this button caused the click, cancel keyup event
|
|
return button.element === target && (cancelKeyup = true);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Keyup event handler, attached to the document.body
|
|
*
|
|
* @param {Event} DOM event object.
|
|
* @param {Object} The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function keyupHandler(event) {
|
|
//hitting enter while button has focus will trigger keyup too.
|
|
//ignore if handled by clickHandler
|
|
if (cancelKeyup) {
|
|
cancelKeyup = false;
|
|
return;
|
|
}
|
|
var instance = openDialogs[openDialogs.length - 1];
|
|
var keyCode = event.keyCode;
|
|
if (instance.__internal.buttons.length === 0 && keyCode === keys.ESC && instance.get('closable') === true) {
|
|
triggerClose(instance);
|
|
return false;
|
|
}else if (usedKeys.indexOf(keyCode) > -1) {
|
|
triggerCallback(instance, function (button) {
|
|
return button.key === keyCode;
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Keydown event handler, attached to the document.body
|
|
*
|
|
* @param {Event} DOM event object.
|
|
* @param {Object} The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function keydownHandler(event) {
|
|
var instance = openDialogs[openDialogs.length - 1];
|
|
var keyCode = event.keyCode;
|
|
if (keyCode === keys.LEFT || keyCode === keys.RIGHT) {
|
|
var buttons = instance.__internal.buttons;
|
|
for (var x = 0; x < buttons.length; x += 1) {
|
|
if (document.activeElement === buttons[x].element) {
|
|
switch (keyCode) {
|
|
case keys.LEFT:
|
|
buttons[(x || buttons.length) - 1].element.focus();
|
|
return;
|
|
case keys.RIGHT:
|
|
buttons[(x + 1) % buttons.length].element.focus();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}else if (keyCode < keys.F12 + 1 && keyCode > keys.F1 - 1 && usedKeys.indexOf(keyCode) > -1) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
triggerCallback(instance, function (button) {
|
|
return button.key === keyCode;
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets focus to proper dialog element
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
* @param {Node} [resetTarget=undefined] DOM element to reset focus to.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function setFocus(instance, resetTarget) {
|
|
// reset target has already been determined.
|
|
if (resetTarget) {
|
|
resetTarget.focus();
|
|
} else {
|
|
// current instance focus settings
|
|
var focus = instance.__internal.focus;
|
|
// the focus element.
|
|
var element = focus.element;
|
|
|
|
switch (typeof focus.element) {
|
|
// a number means a button index
|
|
case 'number':
|
|
if (instance.__internal.buttons.length > focus.element) {
|
|
//in basic view, skip focusing the buttons.
|
|
if (instance.get('basic') === true) {
|
|
element = instance.elements.reset[0];
|
|
} else {
|
|
element = instance.__internal.buttons[focus.element].element;
|
|
}
|
|
}
|
|
break;
|
|
// a string means querySelector to select from dialog body contents.
|
|
case 'string':
|
|
element = instance.elements.body.querySelector(focus.element);
|
|
break;
|
|
// a function should return the focus element.
|
|
case 'function':
|
|
element = focus.element.call(instance);
|
|
break;
|
|
}
|
|
|
|
// if no focus element, default to first reset element.
|
|
if ((typeof element === 'undefined' || element === null) && instance.__internal.buttons.length === 0) {
|
|
element = instance.elements.reset[0];
|
|
}
|
|
// focus
|
|
if (element && element.focus) {
|
|
element.focus();
|
|
// if selectable
|
|
if (focus.select && element.select) {
|
|
element.select();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Focus event handler, attached to document.body and dialogs own reset links.
|
|
* handles the focus for modal dialogs only.
|
|
*
|
|
* @param {Event} event DOM focus event object.
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function onReset(event, instance) {
|
|
|
|
// should work on last modal if triggered from document.body
|
|
if (!instance) {
|
|
for (var x = openDialogs.length - 1; x > -1; x -= 1) {
|
|
if (openDialogs[x].isModal()) {
|
|
instance = openDialogs[x];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// if modal
|
|
if (instance && instance.isModal()) {
|
|
// determine reset target to enable forward/backward tab cycle.
|
|
var resetTarget, target = event.srcElement || event.target;
|
|
var lastResetElement = target === instance.elements.reset[1] || (instance.__internal.buttons.length === 0 && target === document.body);
|
|
|
|
// if last reset link, then go to maximize or close
|
|
if (lastResetElement) {
|
|
if (instance.get('maximizable')) {
|
|
resetTarget = instance.elements.commands.maximize;
|
|
} else if (instance.get('closable')) {
|
|
resetTarget = instance.elements.commands.close;
|
|
}
|
|
}
|
|
// if no reset target found, try finding the best button
|
|
if (resetTarget === undefined) {
|
|
if (typeof instance.__internal.focus.element === 'number') {
|
|
// button focus element, go to first available button
|
|
if (target === instance.elements.reset[0]) {
|
|
resetTarget = instance.elements.buttons.auxiliary.firstChild || instance.elements.buttons.primary.firstChild;
|
|
} else if (lastResetElement) {
|
|
//restart the cycle by going to first reset link
|
|
resetTarget = instance.elements.reset[0];
|
|
}
|
|
} else {
|
|
// will reach here when tapping backwards, so go to last child
|
|
// The focus element SHOULD NOT be a button (logically!).
|
|
if (target === instance.elements.reset[0]) {
|
|
resetTarget = instance.elements.buttons.primary.lastChild || instance.elements.buttons.auxiliary.lastChild;
|
|
}
|
|
}
|
|
}
|
|
// focus
|
|
setFocus(instance, resetTarget);
|
|
}
|
|
}
|
|
/**
|
|
* Transition in transitionend event handler.
|
|
*
|
|
* @param {Event} TransitionEnd event object.
|
|
* @param {Object} The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function handleTransitionInEvent(event, instance) {
|
|
// clear the timer
|
|
clearTimeout(instance.__internal.timerIn);
|
|
|
|
// once transition is complete, set focus
|
|
setFocus(instance);
|
|
|
|
//restore scroll to prevent document jump
|
|
restoreScrollPosition();
|
|
|
|
// allow handling key up after transition ended.
|
|
cancelKeyup = false;
|
|
|
|
// allow custom `onfocus` method
|
|
dispatchEvent('onfocus', instance);
|
|
|
|
// unbind the event
|
|
off(instance.elements.dialog, transition.type, instance.__internal.transitionInHandler);
|
|
|
|
removeClass(instance.elements.root, classes.animationIn);
|
|
}
|
|
|
|
/**
|
|
* Transition out transitionend event handler.
|
|
*
|
|
* @param {Event} TransitionEnd event object.
|
|
* @param {Object} The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function handleTransitionOutEvent(event, instance) {
|
|
// clear the timer
|
|
clearTimeout(instance.__internal.timerOut);
|
|
// unbind the event
|
|
off(instance.elements.dialog, transition.type, instance.__internal.transitionOutHandler);
|
|
|
|
// reset move updates
|
|
resetMove(instance);
|
|
// reset resize updates
|
|
resetResize(instance);
|
|
|
|
// restore if maximized
|
|
if (instance.isMaximized() && !instance.get('startMaximized')) {
|
|
restore(instance);
|
|
}
|
|
|
|
// return focus to the last active element
|
|
if (alertify.defaults.maintainFocus && instance.__internal.activeElement) {
|
|
instance.__internal.activeElement.focus();
|
|
instance.__internal.activeElement = null;
|
|
}
|
|
|
|
//destory the instance
|
|
if (typeof instance.__internal.destroy === 'function') {
|
|
instance.__internal.destroy.apply(instance);
|
|
}
|
|
}
|
|
/* Controls moving a dialog around */
|
|
//holde the current moving instance
|
|
var movable = null,
|
|
//holds the current X offset when move starts
|
|
offsetX = 0,
|
|
//holds the current Y offset when move starts
|
|
offsetY = 0,
|
|
xProp = 'pageX',
|
|
yProp = 'pageY',
|
|
bounds = null,
|
|
refreshTop = false,
|
|
moveDelegate = null
|
|
;
|
|
|
|
/**
|
|
* Helper: sets the element top/left coordinates
|
|
*
|
|
* @param {Event} event DOM event object.
|
|
* @param {Node} element The element being moved.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function moveElement(event, element) {
|
|
var left = (event[xProp] - offsetX),
|
|
top = (event[yProp] - offsetY);
|
|
|
|
if(refreshTop){
|
|
top -= document.body.scrollTop;
|
|
}
|
|
|
|
element.style.left = left + 'px';
|
|
element.style.top = top + 'px';
|
|
|
|
}
|
|
/**
|
|
* Helper: sets the element top/left coordinates within screen bounds
|
|
*
|
|
* @param {Event} event DOM event object.
|
|
* @param {Node} element The element being moved.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function moveElementBounded(event, element) {
|
|
var left = (event[xProp] - offsetX),
|
|
top = (event[yProp] - offsetY);
|
|
|
|
if(refreshTop){
|
|
top -= document.body.scrollTop;
|
|
}
|
|
|
|
element.style.left = Math.min(bounds.maxLeft, Math.max(bounds.minLeft, left)) + 'px';
|
|
if(refreshTop){
|
|
element.style.top = Math.min(bounds.maxTop, Math.max(bounds.minTop, top)) + 'px';
|
|
}else{
|
|
element.style.top = Math.max(bounds.minTop, top) + 'px';
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggers the start of a move event, attached to the header element mouse down event.
|
|
* Adds no-selection class to the body, disabling selection while moving.
|
|
*
|
|
* @param {Event} event DOM event object.
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {Boolean} false
|
|
*/
|
|
function beginMove(event, instance) {
|
|
if (resizable === null && !instance.isMaximized() && instance.get('movable')) {
|
|
var eventSrc, left=0, top=0;
|
|
if (event.type === 'touchstart') {
|
|
event.preventDefault();
|
|
eventSrc = event.targetTouches[0];
|
|
xProp = 'clientX';
|
|
yProp = 'clientY';
|
|
} else if (event.button === 0) {
|
|
eventSrc = event;
|
|
}
|
|
|
|
if (eventSrc) {
|
|
|
|
var element = instance.elements.dialog;
|
|
addClass(element, classes.capture);
|
|
|
|
if (element.style.left) {
|
|
left = parseInt(element.style.left, 10);
|
|
}
|
|
|
|
if (element.style.top) {
|
|
top = parseInt(element.style.top, 10);
|
|
}
|
|
|
|
offsetX = eventSrc[xProp] - left;
|
|
offsetY = eventSrc[yProp] - top;
|
|
|
|
if(instance.isModal()){
|
|
offsetY += instance.elements.modal.scrollTop;
|
|
}else if(instance.isPinned()){
|
|
offsetY -= document.body.scrollTop;
|
|
}
|
|
|
|
if(instance.get('moveBounded')){
|
|
var current = element,
|
|
offsetLeft = -left,
|
|
offsetTop = -top;
|
|
|
|
//calc offset
|
|
do {
|
|
offsetLeft += current.offsetLeft;
|
|
offsetTop += current.offsetTop;
|
|
} while (current = current.offsetParent);
|
|
|
|
bounds = {
|
|
maxLeft : offsetLeft,
|
|
minLeft : -offsetLeft,
|
|
maxTop : document.documentElement.clientHeight - element.clientHeight - offsetTop,
|
|
minTop : -offsetTop
|
|
};
|
|
moveDelegate = moveElementBounded;
|
|
}else{
|
|
bounds = null;
|
|
moveDelegate = moveElement;
|
|
}
|
|
|
|
// allow custom `onmove` method
|
|
dispatchEvent('onmove', instance);
|
|
|
|
refreshTop = !instance.isModal() && instance.isPinned();
|
|
movable = instance;
|
|
moveDelegate(eventSrc, element);
|
|
addClass(document.body, classes.noSelection);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The actual move handler, attached to document.body mousemove event.
|
|
*
|
|
* @param {Event} event DOM event object.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function move(event) {
|
|
if (movable) {
|
|
var eventSrc;
|
|
if (event.type === 'touchmove') {
|
|
event.preventDefault();
|
|
eventSrc = event.targetTouches[0];
|
|
} else if (event.button === 0) {
|
|
eventSrc = event;
|
|
}
|
|
if (eventSrc) {
|
|
moveDelegate(eventSrc, movable.elements.dialog);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggers the end of a move event, attached to document.body mouseup event.
|
|
* Removes no-selection class from document.body, allowing selection.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function endMove() {
|
|
if (movable) {
|
|
var instance = movable;
|
|
movable = bounds = null;
|
|
removeClass(document.body, classes.noSelection);
|
|
removeClass(instance.elements.dialog, classes.capture);
|
|
// allow custom `onmoved` method
|
|
dispatchEvent('onmoved', instance);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets any changes made by moving the element to its original state,
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function resetMove(instance) {
|
|
movable = null;
|
|
var element = instance.elements.dialog;
|
|
element.style.left = element.style.top = '';
|
|
}
|
|
|
|
/**
|
|
* Updates the dialog move behavior.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
* @param {Boolean} on True to add the behavior, removes it otherwise.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function updateMovable(instance) {
|
|
if (instance.get('movable')) {
|
|
// add class
|
|
addClass(instance.elements.root, classes.movable);
|
|
if (instance.isOpen()) {
|
|
bindMovableEvents(instance);
|
|
}
|
|
} else {
|
|
|
|
//reset
|
|
resetMove(instance);
|
|
// remove class
|
|
removeClass(instance.elements.root, classes.movable);
|
|
if (instance.isOpen()) {
|
|
unbindMovableEvents(instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Controls moving a dialog around */
|
|
//holde the current instance being resized
|
|
var resizable = null,
|
|
//holds the staring left offset when resize starts.
|
|
startingLeft = Number.Nan,
|
|
//holds the staring width when resize starts.
|
|
startingWidth = 0,
|
|
//holds the initial width when resized for the first time.
|
|
minWidth = 0,
|
|
//holds the offset of the resize handle.
|
|
handleOffset = 0
|
|
;
|
|
|
|
/**
|
|
* Helper: sets the element width/height and updates left coordinate if neccessary.
|
|
*
|
|
* @param {Event} event DOM mousemove event object.
|
|
* @param {Node} element The element being moved.
|
|
* @param {Boolean} pinned A flag indicating if the element being resized is pinned to the screen.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function resizeElement(event, element, pageRelative) {
|
|
|
|
//calculate offsets from 0,0
|
|
var current = element;
|
|
var offsetLeft = 0;
|
|
var offsetTop = 0;
|
|
do {
|
|
offsetLeft += current.offsetLeft;
|
|
offsetTop += current.offsetTop;
|
|
} while (current = current.offsetParent);
|
|
|
|
// determine X,Y coordinates.
|
|
var X, Y;
|
|
if (pageRelative === true) {
|
|
X = event.pageX;
|
|
Y = event.pageY;
|
|
} else {
|
|
X = event.clientX;
|
|
Y = event.clientY;
|
|
}
|
|
// rtl handling
|
|
var isRTL = isRightToLeft();
|
|
if (isRTL) {
|
|
// reverse X
|
|
X = document.body.offsetWidth - X;
|
|
// if has a starting left, calculate offsetRight
|
|
if (!isNaN(startingLeft)) {
|
|
offsetLeft = document.body.offsetWidth - offsetLeft - element.offsetWidth;
|
|
}
|
|
}
|
|
|
|
// set width/height
|
|
element.style.height = (Y - offsetTop + handleOffset) + 'px';
|
|
element.style.width = (X - offsetLeft + handleOffset) + 'px';
|
|
|
|
// if the element being resized has a starting left, maintain it.
|
|
// the dialog is centered, divide by half the offset to maintain the margins.
|
|
if (!isNaN(startingLeft)) {
|
|
var diff = Math.abs(element.offsetWidth - startingWidth) * 0.5;
|
|
if (isRTL) {
|
|
//negate the diff, why?
|
|
//when growing it should decrease left
|
|
//when shrinking it should increase left
|
|
diff *= -1;
|
|
}
|
|
if (element.offsetWidth > startingWidth) {
|
|
//growing
|
|
element.style.left = (startingLeft + diff) + 'px';
|
|
} else if (element.offsetWidth >= minWidth) {
|
|
//shrinking
|
|
element.style.left = (startingLeft - diff) + 'px';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggers the start of a resize event, attached to the resize handle element mouse down event.
|
|
* Adds no-selection class to the body, disabling selection while moving.
|
|
*
|
|
* @param {Event} event DOM event object.
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {Boolean} false
|
|
*/
|
|
function beginResize(event, instance) {
|
|
if (!instance.isMaximized()) {
|
|
var eventSrc;
|
|
if (event.type === 'touchstart') {
|
|
event.preventDefault();
|
|
eventSrc = event.targetTouches[0];
|
|
} else if (event.button === 0) {
|
|
eventSrc = event;
|
|
}
|
|
if (eventSrc) {
|
|
// allow custom `onresize` method
|
|
dispatchEvent('onresize', instance);
|
|
|
|
resizable = instance;
|
|
handleOffset = instance.elements.resizeHandle.offsetHeight / 2;
|
|
var element = instance.elements.dialog;
|
|
addClass(element, classes.capture);
|
|
startingLeft = parseInt(element.style.left, 10);
|
|
element.style.height = element.offsetHeight + 'px';
|
|
element.style.minHeight = instance.elements.header.offsetHeight + instance.elements.footer.offsetHeight + 'px';
|
|
element.style.width = (startingWidth = element.offsetWidth) + 'px';
|
|
|
|
if (element.style.maxWidth !== 'none') {
|
|
element.style.minWidth = (minWidth = element.offsetWidth) + 'px';
|
|
}
|
|
element.style.maxWidth = 'none';
|
|
addClass(document.body, classes.noSelection);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The actual resize handler, attached to document.body mousemove event.
|
|
*
|
|
* @param {Event} event DOM event object.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function resize(event) {
|
|
if (resizable) {
|
|
var eventSrc;
|
|
if (event.type === 'touchmove') {
|
|
event.preventDefault();
|
|
eventSrc = event.targetTouches[0];
|
|
} else if (event.button === 0) {
|
|
eventSrc = event;
|
|
}
|
|
if (eventSrc) {
|
|
resizeElement(eventSrc, resizable.elements.dialog, !resizable.get('modal') && !resizable.get('pinned'));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggers the end of a resize event, attached to document.body mouseup event.
|
|
* Removes no-selection class from document.body, allowing selection.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function endResize() {
|
|
if (resizable) {
|
|
var instance = resizable;
|
|
resizable = null;
|
|
removeClass(document.body, classes.noSelection);
|
|
removeClass(instance.elements.dialog, classes.capture);
|
|
cancelClick = true;
|
|
// allow custom `onresized` method
|
|
dispatchEvent('onresized', instance);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets any changes made by resizing the element to its original state.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function resetResize(instance) {
|
|
resizable = null;
|
|
var element = instance.elements.dialog;
|
|
if (element.style.maxWidth === 'none') {
|
|
//clear inline styles.
|
|
element.style.maxWidth = element.style.minWidth = element.style.width = element.style.height = element.style.minHeight = element.style.left = '';
|
|
//reset variables.
|
|
startingLeft = Number.Nan;
|
|
startingWidth = minWidth = handleOffset = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates the dialog move behavior.
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
* @param {Boolean} on True to add the behavior, removes it otherwise.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function updateResizable(instance) {
|
|
if (instance.get('resizable')) {
|
|
// add class
|
|
addClass(instance.elements.root, classes.resizable);
|
|
if (instance.isOpen()) {
|
|
bindResizableEvents(instance);
|
|
}
|
|
} else {
|
|
//reset
|
|
resetResize(instance);
|
|
// remove class
|
|
removeClass(instance.elements.root, classes.resizable);
|
|
if (instance.isOpen()) {
|
|
unbindResizableEvents(instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset move/resize on window resize.
|
|
*
|
|
* @param {Event} event window resize event object.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function windowResize(/*event*/) {
|
|
for (var x = 0; x < openDialogs.length; x += 1) {
|
|
var instance = openDialogs[x];
|
|
if (instance.get('autoReset')) {
|
|
resetMove(instance);
|
|
resetResize(instance);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Bind dialogs events
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function bindEvents(instance) {
|
|
// if first dialog, hook global handlers
|
|
if (openDialogs.length === 1) {
|
|
//global
|
|
on(window, 'resize', windowResize);
|
|
on(document.body, 'keyup', keyupHandler);
|
|
on(document.body, 'keydown', keydownHandler);
|
|
on(document.body, 'focus', onReset);
|
|
|
|
//move
|
|
on(document.documentElement, 'mousemove', move);
|
|
on(document.documentElement, 'touchmove', move);
|
|
on(document.documentElement, 'mouseup', endMove);
|
|
on(document.documentElement, 'touchend', endMove);
|
|
//resize
|
|
on(document.documentElement, 'mousemove', resize);
|
|
on(document.documentElement, 'touchmove', resize);
|
|
on(document.documentElement, 'mouseup', endResize);
|
|
on(document.documentElement, 'touchend', endResize);
|
|
}
|
|
|
|
// common events
|
|
on(instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler);
|
|
on(instance.elements.footer, 'click', instance.__internal.buttonsClickHandler);
|
|
on(instance.elements.reset[0], 'focus', instance.__internal.resetHandler);
|
|
on(instance.elements.reset[1], 'focus', instance.__internal.resetHandler);
|
|
|
|
//prevent handling key up when dialog is being opened by a key stroke.
|
|
cancelKeyup = true;
|
|
// hook in transition handler
|
|
on(instance.elements.dialog, transition.type, instance.__internal.transitionInHandler);
|
|
|
|
// modelss only events
|
|
if (!instance.get('modal')) {
|
|
bindModelessEvents(instance);
|
|
}
|
|
|
|
// resizable
|
|
if (instance.get('resizable')) {
|
|
bindResizableEvents(instance);
|
|
}
|
|
|
|
// movable
|
|
if (instance.get('movable')) {
|
|
bindMovableEvents(instance);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unbind dialogs events
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function unbindEvents(instance) {
|
|
// if last dialog, remove global handlers
|
|
if (openDialogs.length === 1) {
|
|
//global
|
|
off(window, 'resize', windowResize);
|
|
off(document.body, 'keyup', keyupHandler);
|
|
off(document.body, 'keydown', keydownHandler);
|
|
off(document.body, 'focus', onReset);
|
|
//move
|
|
off(document.documentElement, 'mousemove', move);
|
|
off(document.documentElement, 'mouseup', endMove);
|
|
//resize
|
|
off(document.documentElement, 'mousemove', resize);
|
|
off(document.documentElement, 'mouseup', endResize);
|
|
}
|
|
|
|
// common events
|
|
off(instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler);
|
|
off(instance.elements.footer, 'click', instance.__internal.buttonsClickHandler);
|
|
off(instance.elements.reset[0], 'focus', instance.__internal.resetHandler);
|
|
off(instance.elements.reset[1], 'focus', instance.__internal.resetHandler);
|
|
|
|
// hook out transition handler
|
|
on(instance.elements.dialog, transition.type, instance.__internal.transitionOutHandler);
|
|
|
|
// modelss only events
|
|
if (!instance.get('modal')) {
|
|
unbindModelessEvents(instance);
|
|
}
|
|
|
|
// movable
|
|
if (instance.get('movable')) {
|
|
unbindMovableEvents(instance);
|
|
}
|
|
|
|
// resizable
|
|
if (instance.get('resizable')) {
|
|
unbindResizableEvents(instance);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Bind modeless specific events
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function bindModelessEvents(instance) {
|
|
on(instance.elements.dialog, 'focus', instance.__internal.bringToFrontHandler, true);
|
|
}
|
|
|
|
/**
|
|
* Unbind modeless specific events
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function unbindModelessEvents(instance) {
|
|
off(instance.elements.dialog, 'focus', instance.__internal.bringToFrontHandler, true);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Bind movable specific events
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function bindMovableEvents(instance) {
|
|
on(instance.elements.header, 'mousedown', instance.__internal.beginMoveHandler);
|
|
on(instance.elements.header, 'touchstart', instance.__internal.beginMoveHandler);
|
|
}
|
|
|
|
/**
|
|
* Unbind movable specific events
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function unbindMovableEvents(instance) {
|
|
off(instance.elements.header, 'mousedown', instance.__internal.beginMoveHandler);
|
|
off(instance.elements.header, 'touchstart', instance.__internal.beginMoveHandler);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Bind resizable specific events
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function bindResizableEvents(instance) {
|
|
on(instance.elements.resizeHandle, 'mousedown', instance.__internal.beginResizeHandler);
|
|
on(instance.elements.resizeHandle, 'touchstart', instance.__internal.beginResizeHandler);
|
|
}
|
|
|
|
/**
|
|
* Unbind resizable specific events
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function unbindResizableEvents(instance) {
|
|
off(instance.elements.resizeHandle, 'mousedown', instance.__internal.beginResizeHandler);
|
|
off(instance.elements.resizeHandle, 'touchstart', instance.__internal.beginResizeHandler);
|
|
}
|
|
|
|
/**
|
|
* Bind closable events
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function bindClosableEvents(instance) {
|
|
on(instance.elements.modal, 'click', instance.__internal.modalClickHandler);
|
|
}
|
|
|
|
/**
|
|
* Unbind closable specific events
|
|
*
|
|
* @param {Object} instance The dilog instance.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function unbindClosableEvents(instance) {
|
|
off(instance.elements.modal, 'click', instance.__internal.modalClickHandler);
|
|
}
|
|
// dialog API
|
|
return {
|
|
__init:initialize,
|
|
/**
|
|
* Check if dialog is currently open
|
|
*
|
|
* @return {Boolean}
|
|
*/
|
|
isOpen: function () {
|
|
return this.__internal.isOpen;
|
|
},
|
|
isModal: function (){
|
|
return this.elements.root.className.indexOf(classes.modeless) < 0;
|
|
},
|
|
isMaximized:function(){
|
|
return this.elements.root.className.indexOf(classes.maximized) > -1;
|
|
},
|
|
isPinned:function(){
|
|
return this.elements.root.className.indexOf(classes.unpinned) < 0;
|
|
},
|
|
maximize:function(){
|
|
if(!this.isMaximized()){
|
|
maximize(this);
|
|
}
|
|
return this;
|
|
},
|
|
restore:function(){
|
|
if(this.isMaximized()){
|
|
restore(this);
|
|
}
|
|
return this;
|
|
},
|
|
pin:function(){
|
|
if(!this.isPinned()){
|
|
pin(this);
|
|
}
|
|
return this;
|
|
},
|
|
unpin:function(){
|
|
if(this.isPinned()){
|
|
unpin(this);
|
|
}
|
|
return this;
|
|
},
|
|
bringToFront:function(){
|
|
bringToFront(null, this);
|
|
return this;
|
|
},
|
|
/**
|
|
* Move the dialog to a specific x/y coordinates
|
|
*
|
|
* @param {Number} x The new dialog x coordinate in pixels.
|
|
* @param {Number} y The new dialog y coordinate in pixels.
|
|
*
|
|
* @return {Object} The dialog instance.
|
|
*/
|
|
moveTo:function(x,y){
|
|
if(!isNaN(x) && !isNaN(y)){
|
|
// allow custom `onmove` method
|
|
dispatchEvent('onmove', this);
|
|
|
|
var element = this.elements.dialog,
|
|
current = element,
|
|
offsetLeft = 0,
|
|
offsetTop = 0;
|
|
|
|
//subtract existing left,top
|
|
if (element.style.left) {
|
|
offsetLeft -= parseInt(element.style.left, 10);
|
|
}
|
|
if (element.style.top) {
|
|
offsetTop -= parseInt(element.style.top, 10);
|
|
}
|
|
//calc offset
|
|
do {
|
|
offsetLeft += current.offsetLeft;
|
|
offsetTop += current.offsetTop;
|
|
} while (current = current.offsetParent);
|
|
|
|
//calc left, top
|
|
var left = (x - offsetLeft);
|
|
var top = (y - offsetTop);
|
|
|
|
//// rtl handling
|
|
if (isRightToLeft()) {
|
|
left *= -1;
|
|
}
|
|
|
|
element.style.left = left + 'px';
|
|
element.style.top = top + 'px';
|
|
|
|
// allow custom `onmoved` method
|
|
dispatchEvent('onmoved', this);
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* Resize the dialog to a specific width/height (the dialog must be 'resizable').
|
|
* The dialog can be resized to:
|
|
* A minimum width equal to the initial display width
|
|
* A minimum height equal to the sum of header/footer heights.
|
|
*
|
|
*
|
|
* @param {Number or String} width The new dialog width in pixels or in percent.
|
|
* @param {Number or String} height The new dialog height in pixels or in percent.
|
|
*
|
|
* @return {Object} The dialog instance.
|
|
*/
|
|
resizeTo:function(width,height){
|
|
var w = parseFloat(width),
|
|
h = parseFloat(height),
|
|
regex = /(\d*\.\d+|\d+)%/
|
|
;
|
|
|
|
if(!isNaN(w) && !isNaN(h) && this.get('resizable') === true){
|
|
|
|
// allow custom `onresize` method
|
|
dispatchEvent('onresize', this);
|
|
|
|
if(('' + width).match(regex)){
|
|
w = w / 100 * document.documentElement.clientWidth ;
|
|
}
|
|
|
|
if(('' + height).match(regex)){
|
|
h = h / 100 * document.documentElement.clientHeight;
|
|
}
|
|
|
|
var element = this.elements.dialog;
|
|
if (element.style.maxWidth !== 'none') {
|
|
element.style.minWidth = (minWidth = element.offsetWidth) + 'px';
|
|
}
|
|
element.style.maxWidth = 'none';
|
|
element.style.minHeight = this.elements.header.offsetHeight + this.elements.footer.offsetHeight + 'px';
|
|
element.style.width = w + 'px';
|
|
element.style.height = h + 'px';
|
|
|
|
// allow custom `onresized` method
|
|
dispatchEvent('onresized', this);
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* Gets or Sets dialog settings/options
|
|
*
|
|
* @param {String|Object} key A string specifying a propery name or a collection of key/value pairs.
|
|
* @param {Object} value Optional, the value associated with the key (in case it was a string).
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
setting : function (key, value) {
|
|
var self = this;
|
|
var result = update(this, this.__internal.options, function(k,o,n){ optionUpdated(self,k,o,n); }, key, value);
|
|
if(result.op === 'get'){
|
|
if(result.found){
|
|
return result.value;
|
|
}else if(typeof this.settings !== 'undefined'){
|
|
return update(this, this.settings, this.settingUpdated || function(){}, key, value).value;
|
|
}else{
|
|
return undefined;
|
|
}
|
|
}else if(result.op === 'set'){
|
|
if(result.items.length > 0){
|
|
var callback = this.settingUpdated || function(){};
|
|
for(var x=0;x<result.items.length;x+=1){
|
|
var item = result.items[x];
|
|
if(!item.found && typeof this.settings !== 'undefined'){
|
|
update(this, this.settings, callback, item.key, item.value);
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
},
|
|
/**
|
|
* [Alias] Sets dialog settings/options
|
|
*/
|
|
set:function(key, value){
|
|
this.setting(key,value);
|
|
return this;
|
|
},
|
|
/**
|
|
* [Alias] Gets dialog settings/options
|
|
*/
|
|
get:function(key){
|
|
return this.setting(key);
|
|
},
|
|
/**
|
|
* Sets dialog header
|
|
* @content {string or element}
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
setHeader:function(content){
|
|
if(typeof content === 'string'){
|
|
clearContents(this.elements.header);
|
|
this.elements.header.innerHTML = content;
|
|
}else if (content instanceof window.HTMLElement && this.elements.header.firstChild !== content){
|
|
clearContents(this.elements.header);
|
|
this.elements.header.appendChild(content);
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* Sets dialog contents
|
|
* @content {string or element}
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
setContent:function(content){
|
|
if(typeof content === 'string'){
|
|
clearContents(this.elements.content);
|
|
this.elements.content.innerHTML = content;
|
|
}else if (content instanceof window.HTMLElement && this.elements.content.firstChild !== content){
|
|
clearContents(this.elements.content);
|
|
this.elements.content.appendChild(content);
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* Show the dialog as modal
|
|
*
|
|
* @return {Object} the dialog instance.
|
|
*/
|
|
showModal: function(className){
|
|
return this.show(true, className);
|
|
},
|
|
/**
|
|
* Show the dialog
|
|
*
|
|
* @return {Object} the dialog instance.
|
|
*/
|
|
show: function (modal, className) {
|
|
|
|
// ensure initialization
|
|
initialize(this);
|
|
|
|
if ( !this.__internal.isOpen ) {
|
|
|
|
// add to open dialogs
|
|
this.__internal.isOpen = true;
|
|
openDialogs.push(this);
|
|
|
|
// save last focused element
|
|
if(alertify.defaults.maintainFocus){
|
|
this.__internal.activeElement = document.activeElement;
|
|
}
|
|
|
|
// set tabindex attribute on body element this allows script to give it focusable
|
|
if(!document.body.hasAttribute('tabindex')) {
|
|
document.body.setAttribute( 'tabindex', tabindex = '0');
|
|
}
|
|
|
|
//allow custom dom manipulation updates before showing the dialog.
|
|
if(typeof this.prepare === 'function'){
|
|
this.prepare();
|
|
}
|
|
|
|
bindEvents(this);
|
|
|
|
if(modal !== undefined){
|
|
this.set('modal', modal);
|
|
}
|
|
|
|
//save scroll to prevent document jump
|
|
saveScrollPosition();
|
|
|
|
ensureNoOverflow();
|
|
|
|
// allow custom dialog class on show
|
|
if(typeof className === 'string' && className !== ''){
|
|
this.__internal.className = className;
|
|
addClass(this.elements.root, className);
|
|
}
|
|
|
|
// maximize if start maximized
|
|
if ( this.get('startMaximized')) {
|
|
this.maximize();
|
|
}else if(this.isMaximized()){
|
|
restore(this);
|
|
}
|
|
|
|
updateAbsPositionFix(this);
|
|
this.elements.root.removeAttribute('style');
|
|
removeClass(this.elements.root, classes.animationOut);
|
|
addClass(this.elements.root, classes.animationIn);
|
|
|
|
// set 1s fallback in case transition event doesn't fire
|
|
clearTimeout( this.__internal.timerIn);
|
|
this.__internal.timerIn = setTimeout( this.__internal.transitionInHandler, transition.supported ? 1000 : 100 );
|
|
|
|
if(isSafari){
|
|
// force desktop safari reflow
|
|
var root = this.elements.root;
|
|
root.style.display = 'none';
|
|
setTimeout(function(){root.style.display = 'block';}, 0);
|
|
}
|
|
|
|
//reflow
|
|
reflow = this.elements.root.offsetWidth;
|
|
|
|
// show dialog
|
|
removeClass(this.elements.root, classes.hidden);
|
|
|
|
// internal on show event
|
|
if(typeof this.hooks.onshow === 'function'){
|
|
this.hooks.onshow.call(this);
|
|
}
|
|
|
|
// allow custom `onshow` method
|
|
dispatchEvent('onshow', this);
|
|
|
|
}else{
|
|
// reset move updates
|
|
resetMove(this);
|
|
// reset resize updates
|
|
resetResize(this);
|
|
// shake the dialog to indicate its already open
|
|
addClass(this.elements.dialog, classes.shake);
|
|
var self = this;
|
|
setTimeout(function(){
|
|
removeClass(self.elements.dialog, classes.shake);
|
|
},200);
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* Close the dialog
|
|
*
|
|
* @return {Object} The dialog instance
|
|
*/
|
|
close: function () {
|
|
if (this.__internal.isOpen ) {
|
|
// custom `onclosing` event
|
|
if(dispatchEvent('onclosing', this) !== false){
|
|
|
|
unbindEvents(this);
|
|
|
|
removeClass(this.elements.root, classes.animationIn);
|
|
addClass(this.elements.root, classes.animationOut);
|
|
|
|
// set 1s fallback in case transition event doesn't fire
|
|
clearTimeout( this.__internal.timerOut );
|
|
this.__internal.timerOut = setTimeout( this.__internal.transitionOutHandler, transition.supported ? 1000 : 100 );
|
|
// hide dialog
|
|
addClass(this.elements.root, classes.hidden);
|
|
//reflow
|
|
reflow = this.elements.modal.offsetWidth;
|
|
|
|
// remove custom dialog class on hide
|
|
if (typeof this.__internal.className !== 'undefined' && this.__internal.className !== '') {
|
|
removeClass(this.elements.root, this.__internal.className);
|
|
}
|
|
|
|
// internal on close event
|
|
if(typeof this.hooks.onclose === 'function'){
|
|
this.hooks.onclose.call(this);
|
|
}
|
|
|
|
// allow custom `onclose` method
|
|
dispatchEvent('onclose', this);
|
|
|
|
//remove from open dialogs
|
|
openDialogs.splice(openDialogs.indexOf(this),1);
|
|
this.__internal.isOpen = false;
|
|
|
|
ensureNoOverflow();
|
|
}
|
|
|
|
}
|
|
// last dialog and tab index was set by us, remove it.
|
|
if(!openDialogs.length && tabindex === '0'){
|
|
document.body.removeAttribute('tabindex');
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* Close all open dialogs except this.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
closeOthers:function(){
|
|
alertify.closeAll(this);
|
|
return this;
|
|
},
|
|
/**
|
|
* Destroys this dialog instance
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
destroy:function(){
|
|
if(this.__internal) {
|
|
if (this.__internal.isOpen ) {
|
|
//mark dialog for destruction, this will be called on tranistionOut event.
|
|
this.__internal.destroy = function(){
|
|
destruct(this, initialize);
|
|
};
|
|
//close the dialog to unbind all events.
|
|
this.close();
|
|
}else if(!this.__internal.destroy){
|
|
destruct(this, initialize);
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
};
|
|
} () );
|
|
var notifier = (function () {
|
|
var reflow,
|
|
element,
|
|
openInstances = [],
|
|
classes = {
|
|
base: 'alertify-notifier',
|
|
message: 'ajs-message',
|
|
top: 'ajs-top',
|
|
right: 'ajs-right',
|
|
bottom: 'ajs-bottom',
|
|
left: 'ajs-left',
|
|
center: 'ajs-center',
|
|
visible: 'ajs-visible',
|
|
hidden: 'ajs-hidden',
|
|
close: 'ajs-close'
|
|
};
|
|
/**
|
|
* Helper: initializes the notifier instance
|
|
*
|
|
*/
|
|
function initialize(instance) {
|
|
|
|
if (!instance.__internal) {
|
|
instance.__internal = {
|
|
position: alertify.defaults.notifier.position,
|
|
delay: alertify.defaults.notifier.delay,
|
|
};
|
|
|
|
element = document.createElement('DIV');
|
|
|
|
updatePosition(instance);
|
|
}
|
|
|
|
//add to DOM tree.
|
|
if (element.parentNode !== document.body) {
|
|
document.body.appendChild(element);
|
|
}
|
|
}
|
|
|
|
function pushInstance(instance) {
|
|
instance.__internal.pushed = true;
|
|
openInstances.push(instance);
|
|
}
|
|
function popInstance(instance) {
|
|
openInstances.splice(openInstances.indexOf(instance), 1);
|
|
instance.__internal.pushed = false;
|
|
}
|
|
/**
|
|
* Helper: update the notifier instance position
|
|
*
|
|
*/
|
|
function updatePosition(instance) {
|
|
element.className = classes.base;
|
|
switch (instance.__internal.position) {
|
|
case 'top-right':
|
|
addClass(element, classes.top + ' ' + classes.right);
|
|
break;
|
|
case 'top-left':
|
|
addClass(element, classes.top + ' ' + classes.left);
|
|
break;
|
|
case 'top-center':
|
|
addClass(element, classes.top + ' ' + classes.center);
|
|
break;
|
|
case 'bottom-left':
|
|
addClass(element, classes.bottom + ' ' + classes.left);
|
|
break;
|
|
case 'bottom-center':
|
|
addClass(element, classes.bottom + ' ' + classes.center);
|
|
break;
|
|
|
|
default:
|
|
case 'bottom-right':
|
|
addClass(element, classes.bottom + ' ' + classes.right);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* creates a new notification message
|
|
*
|
|
* @param {DOMElement} message The notifier message element
|
|
* @param {Number} wait Time (in ms) to wait before the message is dismissed, a value of 0 means keep open till clicked.
|
|
* @param {Function} callback A callback function to be invoked when the message is dismissed.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
function create(div, callback) {
|
|
|
|
function clickDelegate(event, instance) {
|
|
if(!instance.__internal.closeButton || event.target.getAttribute('data-close') === 'true'){
|
|
instance.dismiss(true);
|
|
}
|
|
}
|
|
|
|
function transitionDone(event, instance) {
|
|
// unbind event
|
|
off(instance.element, transition.type, transitionDone);
|
|
// remove the message
|
|
element.removeChild(instance.element);
|
|
}
|
|
|
|
function initialize(instance) {
|
|
if (!instance.__internal) {
|
|
instance.__internal = {
|
|
pushed: false,
|
|
delay : undefined,
|
|
timer: undefined,
|
|
clickHandler: undefined,
|
|
transitionEndHandler: undefined,
|
|
transitionTimeout: undefined
|
|
};
|
|
instance.__internal.clickHandler = delegate(instance, clickDelegate);
|
|
instance.__internal.transitionEndHandler = delegate(instance, transitionDone);
|
|
}
|
|
return instance;
|
|
}
|
|
function clearTimers(instance) {
|
|
clearTimeout(instance.__internal.timer);
|
|
clearTimeout(instance.__internal.transitionTimeout);
|
|
}
|
|
return initialize({
|
|
/* notification DOM element*/
|
|
element: div,
|
|
/*
|
|
* Pushes a notification message
|
|
* @param {string or DOMElement} content The notification message content
|
|
* @param {Number} wait The time (in seconds) to wait before the message is dismissed, a value of 0 means keep open till clicked.
|
|
*
|
|
*/
|
|
push: function (_content, _wait) {
|
|
if (!this.__internal.pushed) {
|
|
|
|
pushInstance(this);
|
|
clearTimers(this);
|
|
|
|
var content, wait;
|
|
switch (arguments.length) {
|
|
case 0:
|
|
wait = this.__internal.delay;
|
|
break;
|
|
case 1:
|
|
if (typeof (_content) === 'number') {
|
|
wait = _content;
|
|
} else {
|
|
content = _content;
|
|
wait = this.__internal.delay;
|
|
}
|
|
break;
|
|
case 2:
|
|
content = _content;
|
|
wait = _wait;
|
|
break;
|
|
}
|
|
this.__internal.closeButton = alertify.defaults.notifier.closeButton;
|
|
// set contents
|
|
if (typeof content !== 'undefined') {
|
|
this.setContent(content);
|
|
}
|
|
// append or insert
|
|
if (notifier.__internal.position.indexOf('top') < 0) {
|
|
element.appendChild(this.element);
|
|
} else {
|
|
element.insertBefore(this.element, element.firstChild);
|
|
}
|
|
reflow = this.element.offsetWidth;
|
|
addClass(this.element, classes.visible);
|
|
// attach click event
|
|
on(this.element, 'click', this.__internal.clickHandler);
|
|
return this.delay(wait);
|
|
}
|
|
return this;
|
|
},
|
|
/*
|
|
* {Function} callback function to be invoked before dismissing the notification message.
|
|
* Remarks: A return value === 'false' will cancel the dismissal
|
|
*
|
|
*/
|
|
ondismiss: function () { },
|
|
/*
|
|
* {Function} callback function to be invoked when the message is dismissed.
|
|
*
|
|
*/
|
|
callback: callback,
|
|
/*
|
|
* Dismisses the notification message
|
|
* @param {Boolean} clicked A flag indicating if the dismissal was caused by a click.
|
|
*
|
|
*/
|
|
dismiss: function (clicked) {
|
|
if (this.__internal.pushed) {
|
|
clearTimers(this);
|
|
if (!(typeof this.ondismiss === 'function' && this.ondismiss.call(this) === false)) {
|
|
//detach click event
|
|
off(this.element, 'click', this.__internal.clickHandler);
|
|
// ensure element exists
|
|
if (typeof this.element !== 'undefined' && this.element.parentNode === element) {
|
|
//transition end or fallback
|
|
this.__internal.transitionTimeout = setTimeout(this.__internal.transitionEndHandler, transition.supported ? 1000 : 100);
|
|
removeClass(this.element, classes.visible);
|
|
|
|
// custom callback on dismiss
|
|
if (typeof this.callback === 'function') {
|
|
this.callback.call(this, clicked);
|
|
}
|
|
}
|
|
popInstance(this);
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
/*
|
|
* Delays the notification message dismissal
|
|
* @param {Number} wait The time (in seconds) to wait before the message is dismissed, a value of 0 means keep open till clicked.
|
|
*
|
|
*/
|
|
delay: function (wait) {
|
|
clearTimers(this);
|
|
this.__internal.delay = typeof wait !== 'undefined' && !isNaN(+wait) ? +wait : notifier.__internal.delay;
|
|
if (this.__internal.delay > 0) {
|
|
var self = this;
|
|
this.__internal.timer = setTimeout(function () { self.dismiss(); }, this.__internal.delay * 1000);
|
|
}
|
|
return this;
|
|
},
|
|
/*
|
|
* Sets the notification message contents
|
|
* @param {string or DOMElement} content The notification message content
|
|
*
|
|
*/
|
|
setContent: function (content) {
|
|
if (typeof content === 'string') {
|
|
clearContents(this.element);
|
|
this.element.innerHTML = content;
|
|
} else if (content instanceof window.HTMLElement && this.element.firstChild !== content) {
|
|
clearContents(this.element);
|
|
this.element.appendChild(content);
|
|
}
|
|
if(this.__internal.closeButton){
|
|
var close = document.createElement('span');
|
|
addClass(close, classes.close);
|
|
close.setAttribute('data-close', true);
|
|
this.element.appendChild(close);
|
|
}
|
|
return this;
|
|
},
|
|
/*
|
|
* Dismisses all open notifications except this.
|
|
*
|
|
*/
|
|
dismissOthers: function () {
|
|
notifier.dismissAll(this);
|
|
return this;
|
|
}
|
|
});
|
|
}
|
|
|
|
//notifier api
|
|
return {
|
|
/**
|
|
* Gets or Sets notifier settings.
|
|
*
|
|
* @param {string} key The setting name
|
|
* @param {Variant} value The setting value.
|
|
*
|
|
* @return {Object} if the called as a setter, return the notifier instance.
|
|
*/
|
|
setting: function (key, value) {
|
|
//ensure init
|
|
initialize(this);
|
|
|
|
if (typeof value === 'undefined') {
|
|
//get
|
|
return this.__internal[key];
|
|
} else {
|
|
//set
|
|
switch (key) {
|
|
case 'position':
|
|
this.__internal.position = value;
|
|
updatePosition(this);
|
|
break;
|
|
case 'delay':
|
|
this.__internal.delay = value;
|
|
break;
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* [Alias] Sets dialog settings/options
|
|
*/
|
|
set:function(key,value){
|
|
this.setting(key,value);
|
|
return this;
|
|
},
|
|
/**
|
|
* [Alias] Gets dialog settings/options
|
|
*/
|
|
get:function(key){
|
|
return this.setting(key);
|
|
},
|
|
/**
|
|
* Creates a new notification message
|
|
*
|
|
* @param {string} type The type of notification message (simply a CSS class name 'ajs-{type}' to be added).
|
|
* @param {Function} callback A callback function to be invoked when the message is dismissed.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
create: function (type, callback) {
|
|
//ensure notifier init
|
|
initialize(this);
|
|
//create new notification message
|
|
var div = document.createElement('div');
|
|
div.className = classes.message + ((typeof type === 'string' && type !== '') ? ' ajs-' + type : '');
|
|
return create(div, callback);
|
|
},
|
|
/**
|
|
* Dismisses all open notifications.
|
|
*
|
|
* @param {Object} excpet [optional] The notification object to exclude from dismissal.
|
|
*
|
|
*/
|
|
dismissAll: function (except) {
|
|
var clone = openInstances.slice(0);
|
|
for (var x = 0; x < clone.length; x += 1) {
|
|
var instance = clone[x];
|
|
if (except === undefined || except !== instance) {
|
|
instance.dismiss();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
})();
|
|
|
|
/**
|
|
* Alertify public API
|
|
* This contains everything that is exposed through the alertify object.
|
|
*
|
|
* @return {Object}
|
|
*/
|
|
function Alertify() {
|
|
|
|
// holds a references of created dialogs
|
|
var dialogs = {};
|
|
|
|
/**
|
|
* Extends a given prototype by merging properties from base into sub.
|
|
*
|
|
* @sub {Object} sub The prototype being overwritten.
|
|
* @base {Object} base The prototype being written.
|
|
*
|
|
* @return {Object} The extended prototype.
|
|
*/
|
|
function extend(sub, base) {
|
|
// copy dialog pototype over definition.
|
|
for (var prop in base) {
|
|
if (base.hasOwnProperty(prop)) {
|
|
sub[prop] = base[prop];
|
|
}
|
|
}
|
|
return sub;
|
|
}
|
|
|
|
|
|
/**
|
|
* Helper: returns a dialog instance from saved dialogs.
|
|
* and initializes the dialog if its not already initialized.
|
|
*
|
|
* @name {String} name The dialog name.
|
|
*
|
|
* @return {Object} The dialog instance.
|
|
*/
|
|
function get_dialog(name) {
|
|
var dialog = dialogs[name].dialog;
|
|
//initialize the dialog if its not already initialized.
|
|
if (dialog && typeof dialog.__init === 'function') {
|
|
dialog.__init(dialog);
|
|
}
|
|
return dialog;
|
|
}
|
|
|
|
/**
|
|
* Helper: registers a new dialog definition.
|
|
*
|
|
* @name {String} name The dialog name.
|
|
* @Factory {Function} Factory a function resposible for creating dialog prototype.
|
|
* @transient {Boolean} transient True to create a new dialog instance each time the dialog is invoked, false otherwise.
|
|
* @base {String} base the name of another dialog to inherit from.
|
|
*
|
|
* @return {Object} The dialog definition.
|
|
*/
|
|
function register(name, Factory, transient, base) {
|
|
var definition = {
|
|
dialog: null,
|
|
factory: Factory
|
|
};
|
|
|
|
//if this is based on an existing dialog, create a new definition
|
|
//by applying the new protoype over the existing one.
|
|
if (base !== undefined) {
|
|
definition.factory = function () {
|
|
return extend(new dialogs[base].factory(), new Factory());
|
|
};
|
|
}
|
|
|
|
if (!transient) {
|
|
//create a new definition based on dialog
|
|
definition.dialog = extend(new definition.factory(), dialog);
|
|
}
|
|
return dialogs[name] = definition;
|
|
}
|
|
|
|
return {
|
|
/**
|
|
* Alertify defaults
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
defaults: defaults,
|
|
/**
|
|
* Dialogs factory
|
|
*
|
|
* @param {string} Dialog name.
|
|
* @param {Function} A Dialog factory function.
|
|
* @param {Boolean} Indicates whether to create a singleton or transient dialog.
|
|
* @param {String} The name of the base type to inherit from.
|
|
*/
|
|
dialog: function (name, Factory, transient, base) {
|
|
|
|
// get request, create a new instance and return it.
|
|
if (typeof Factory !== 'function') {
|
|
return get_dialog(name);
|
|
}
|
|
|
|
if (this.hasOwnProperty(name)) {
|
|
throw new Error('alertify.dialog: name already exists');
|
|
}
|
|
|
|
// register the dialog
|
|
var definition = register(name, Factory, transient, base);
|
|
|
|
if (transient) {
|
|
|
|
// make it public
|
|
this[name] = function () {
|
|
//if passed with no params, consider it a get request
|
|
if (arguments.length === 0) {
|
|
return definition.dialog;
|
|
} else {
|
|
var instance = extend(new definition.factory(), dialog);
|
|
//ensure init
|
|
if (instance && typeof instance.__init === 'function') {
|
|
instance.__init(instance);
|
|
}
|
|
instance['main'].apply(instance, arguments);
|
|
return instance['show'].apply(instance);
|
|
}
|
|
};
|
|
} else {
|
|
// make it public
|
|
this[name] = function () {
|
|
//ensure init
|
|
if (definition.dialog && typeof definition.dialog.__init === 'function') {
|
|
definition.dialog.__init(definition.dialog);
|
|
}
|
|
//if passed with no params, consider it a get request
|
|
if (arguments.length === 0) {
|
|
return definition.dialog;
|
|
} else {
|
|
var dialog = definition.dialog;
|
|
dialog['main'].apply(definition.dialog, arguments);
|
|
return dialog['show'].apply(definition.dialog);
|
|
}
|
|
};
|
|
}
|
|
},
|
|
/**
|
|
* Close all open dialogs.
|
|
*
|
|
* @param {Object} excpet [optional] The dialog object to exclude from closing.
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
closeAll: function (except) {
|
|
var clone = openDialogs.slice(0);
|
|
for (var x = 0; x < clone.length; x += 1) {
|
|
var instance = clone[x];
|
|
if (except === undefined || except !== instance) {
|
|
instance.close();
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* Gets or Sets dialog settings/options. if the dialog is transient, this call does nothing.
|
|
*
|
|
* @param {string} name The dialog name.
|
|
* @param {String|Object} key A string specifying a propery name or a collection of key/value pairs.
|
|
* @param {Variant} value Optional, the value associated with the key (in case it was a string).
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
setting: function (name, key, value) {
|
|
|
|
if (name === 'notifier') {
|
|
return notifier.setting(key, value);
|
|
}
|
|
|
|
var dialog = get_dialog(name);
|
|
if (dialog) {
|
|
return dialog.setting(key, value);
|
|
}
|
|
},
|
|
/**
|
|
* [Alias] Sets dialog settings/options
|
|
*/
|
|
set: function(name,key,value){
|
|
return this.setting(name, key,value);
|
|
},
|
|
/**
|
|
* [Alias] Gets dialog settings/options
|
|
*/
|
|
get: function(name, key){
|
|
return this.setting(name, key);
|
|
},
|
|
/**
|
|
* Creates a new notification message.
|
|
* If a type is passed, a class name "ajs-{type}" will be added.
|
|
* This allows for custom look and feel for various types of notifications.
|
|
*
|
|
* @param {String | DOMElement} [message=undefined] Message text
|
|
* @param {String} [type=''] Type of log message
|
|
* @param {String} [wait=''] Time (in seconds) to wait before auto-close
|
|
* @param {Function} [callback=undefined] A callback function to be invoked when the log is closed.
|
|
*
|
|
* @return {Object} Notification object.
|
|
*/
|
|
notify: function (message, type, wait, callback) {
|
|
return notifier.create(type, callback).push(message, wait);
|
|
},
|
|
/**
|
|
* Creates a new notification message.
|
|
*
|
|
* @param {String} [message=undefined] Message text
|
|
* @param {String} [wait=''] Time (in seconds) to wait before auto-close
|
|
* @param {Function} [callback=undefined] A callback function to be invoked when the log is closed.
|
|
*
|
|
* @return {Object} Notification object.
|
|
*/
|
|
message: function (message, wait, callback) {
|
|
return notifier.create(null, callback).push(message, wait);
|
|
},
|
|
/**
|
|
* Creates a new notification message of type 'success'.
|
|
*
|
|
* @param {String} [message=undefined] Message text
|
|
* @param {String} [wait=''] Time (in seconds) to wait before auto-close
|
|
* @param {Function} [callback=undefined] A callback function to be invoked when the log is closed.
|
|
*
|
|
* @return {Object} Notification object.
|
|
*/
|
|
success: function (message, wait, callback) {
|
|
return notifier.create('success', callback).push(message, wait);
|
|
},
|
|
/**
|
|
* Creates a new notification message of type 'error'.
|
|
*
|
|
* @param {String} [message=undefined] Message text
|
|
* @param {String} [wait=''] Time (in seconds) to wait before auto-close
|
|
* @param {Function} [callback=undefined] A callback function to be invoked when the log is closed.
|
|
*
|
|
* @return {Object} Notification object.
|
|
*/
|
|
error: function (message, wait, callback) {
|
|
return notifier.create('error', callback).push(message, wait);
|
|
},
|
|
/**
|
|
* Creates a new notification message of type 'warning'.
|
|
*
|
|
* @param {String} [message=undefined] Message text
|
|
* @param {String} [wait=''] Time (in seconds) to wait before auto-close
|
|
* @param {Function} [callback=undefined] A callback function to be invoked when the log is closed.
|
|
*
|
|
* @return {Object} Notification object.
|
|
*/
|
|
warning: function (message, wait, callback) {
|
|
return notifier.create('warning', callback).push(message, wait);
|
|
},
|
|
/**
|
|
* Dismisses all open notifications
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
dismissAll: function () {
|
|
notifier.dismissAll();
|
|
}
|
|
};
|
|
}
|
|
var alertify = new Alertify();
|
|
|
|
/**
|
|
* Alert dialog definition
|
|
*
|
|
* invoked by:
|
|
* alertify.alert(message);
|
|
* alertify.alert(title, message);
|
|
* alertify.alert(message, onok);
|
|
* alertify.alert(title, message, onok);
|
|
*/
|
|
alertify.dialog('alert', function () {
|
|
return {
|
|
main: function (_title, _message, _onok) {
|
|
var title, message, onok;
|
|
switch (arguments.length) {
|
|
case 1:
|
|
message = _title;
|
|
break;
|
|
case 2:
|
|
if (typeof _message === 'function') {
|
|
message = _title;
|
|
onok = _message;
|
|
} else {
|
|
title = _title;
|
|
message = _message;
|
|
}
|
|
break;
|
|
case 3:
|
|
title = _title;
|
|
message = _message;
|
|
onok = _onok;
|
|
break;
|
|
}
|
|
this.set('title', title);
|
|
this.set('message', message);
|
|
this.set('onok', onok);
|
|
return this;
|
|
},
|
|
setup: function () {
|
|
return {
|
|
buttons: [
|
|
{
|
|
text: alertify.defaults.glossary.ok,
|
|
key: keys.ESC,
|
|
invokeOnClose: true,
|
|
className: alertify.defaults.theme.ok,
|
|
}
|
|
],
|
|
focus: {
|
|
element: 0,
|
|
select: false
|
|
},
|
|
options: {
|
|
maximizable: false,
|
|
resizable: false
|
|
}
|
|
};
|
|
},
|
|
build: function () {
|
|
// nothing
|
|
},
|
|
prepare: function () {
|
|
//nothing
|
|
},
|
|
setMessage: function (message) {
|
|
this.setContent(message);
|
|
},
|
|
settings: {
|
|
message: undefined,
|
|
onok: undefined,
|
|
label: undefined,
|
|
},
|
|
settingUpdated: function (key, oldValue, newValue) {
|
|
switch (key) {
|
|
case 'message':
|
|
this.setMessage(newValue);
|
|
break;
|
|
case 'label':
|
|
if (this.__internal.buttons[0].element) {
|
|
this.__internal.buttons[0].element.innerHTML = newValue;
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
callback: function (closeEvent) {
|
|
if (typeof this.get('onok') === 'function') {
|
|
var returnValue = this.get('onok').call(this, closeEvent);
|
|
if (typeof returnValue !== 'undefined') {
|
|
closeEvent.cancel = !returnValue;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
});
|
|
/**
|
|
* Confirm dialog object
|
|
*
|
|
* alertify.confirm(message);
|
|
* alertify.confirm(message, onok);
|
|
* alertify.confirm(message, onok, oncancel);
|
|
* alertify.confirm(title, message, onok, oncancel);
|
|
*/
|
|
alertify.dialog('confirm', function () {
|
|
|
|
var autoConfirm = {
|
|
timer: null,
|
|
index: null,
|
|
text: null,
|
|
duration: null,
|
|
task: function (event, self) {
|
|
if (self.isOpen()) {
|
|
self.__internal.buttons[autoConfirm.index].element.innerHTML = autoConfirm.text + ' (‏' + autoConfirm.duration + '‏) ';
|
|
autoConfirm.duration -= 1;
|
|
if (autoConfirm.duration === -1) {
|
|
clearAutoConfirm(self);
|
|
var button = self.__internal.buttons[autoConfirm.index];
|
|
var closeEvent = createCloseEvent(autoConfirm.index, button);
|
|
|
|
if (typeof self.callback === 'function') {
|
|
self.callback.apply(self, [closeEvent]);
|
|
}
|
|
//close the dialog.
|
|
if (closeEvent.close !== false) {
|
|
self.close();
|
|
}
|
|
}
|
|
} else {
|
|
clearAutoConfirm(self);
|
|
}
|
|
}
|
|
};
|
|
|
|
function clearAutoConfirm(self) {
|
|
if (autoConfirm.timer !== null) {
|
|
clearInterval(autoConfirm.timer);
|
|
autoConfirm.timer = null;
|
|
self.__internal.buttons[autoConfirm.index].element.innerHTML = autoConfirm.text;
|
|
}
|
|
}
|
|
|
|
function startAutoConfirm(self, index, duration) {
|
|
clearAutoConfirm(self);
|
|
autoConfirm.duration = duration;
|
|
autoConfirm.index = index;
|
|
autoConfirm.text = self.__internal.buttons[index].element.innerHTML;
|
|
autoConfirm.timer = setInterval(delegate(self, autoConfirm.task), 1000);
|
|
autoConfirm.task(null, self);
|
|
}
|
|
|
|
|
|
return {
|
|
main: function (_title, _message, _onok, _oncancel) {
|
|
var title, message, onok, oncancel;
|
|
switch (arguments.length) {
|
|
case 1:
|
|
message = _title;
|
|
break;
|
|
case 2:
|
|
message = _title;
|
|
onok = _message;
|
|
break;
|
|
case 3:
|
|
message = _title;
|
|
onok = _message;
|
|
oncancel = _onok;
|
|
break;
|
|
case 4:
|
|
title = _title;
|
|
message = _message;
|
|
onok = _onok;
|
|
oncancel = _oncancel;
|
|
break;
|
|
}
|
|
this.set('title', title);
|
|
this.set('message', message);
|
|
this.set('onok', onok);
|
|
this.set('oncancel', oncancel);
|
|
return this;
|
|
},
|
|
setup: function () {
|
|
return {
|
|
buttons: [
|
|
{
|
|
text: alertify.defaults.glossary.ok,
|
|
key: keys.ENTER,
|
|
className: alertify.defaults.theme.ok,
|
|
},
|
|
{
|
|
text: alertify.defaults.glossary.cancel,
|
|
key: keys.ESC,
|
|
invokeOnClose: true,
|
|
className: alertify.defaults.theme.cancel,
|
|
}
|
|
],
|
|
focus: {
|
|
element: 0,
|
|
select: false
|
|
},
|
|
options: {
|
|
maximizable: false,
|
|
resizable: false
|
|
}
|
|
};
|
|
},
|
|
build: function () {
|
|
//nothing
|
|
},
|
|
prepare: function () {
|
|
//nothing
|
|
},
|
|
setMessage: function (message) {
|
|
this.setContent(message);
|
|
},
|
|
settings: {
|
|
message: null,
|
|
labels: null,
|
|
onok: null,
|
|
oncancel: null,
|
|
defaultFocus: null,
|
|
reverseButtons: null,
|
|
},
|
|
settingUpdated: function (key, oldValue, newValue) {
|
|
switch (key) {
|
|
case 'message':
|
|
this.setMessage(newValue);
|
|
break;
|
|
case 'labels':
|
|
if ('ok' in newValue && this.__internal.buttons[0].element) {
|
|
this.__internal.buttons[0].text = newValue.ok;
|
|
this.__internal.buttons[0].element.innerHTML = newValue.ok;
|
|
}
|
|
if ('cancel' in newValue && this.__internal.buttons[1].element) {
|
|
this.__internal.buttons[1].text = newValue.cancel;
|
|
this.__internal.buttons[1].element.innerHTML = newValue.cancel;
|
|
}
|
|
break;
|
|
case 'reverseButtons':
|
|
if (newValue === true) {
|
|
this.elements.buttons.primary.appendChild(this.__internal.buttons[0].element);
|
|
} else {
|
|
this.elements.buttons.primary.appendChild(this.__internal.buttons[1].element);
|
|
}
|
|
break;
|
|
case 'defaultFocus':
|
|
this.__internal.focus.element = newValue === 'ok' ? 0 : 1;
|
|
break;
|
|
}
|
|
},
|
|
callback: function (closeEvent) {
|
|
clearAutoConfirm(this);
|
|
var returnValue;
|
|
switch (closeEvent.index) {
|
|
case 0:
|
|
if (typeof this.get('onok') === 'function') {
|
|
returnValue = this.get('onok').call(this, closeEvent);
|
|
if (typeof returnValue !== 'undefined') {
|
|
closeEvent.cancel = !returnValue;
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
if (typeof this.get('oncancel') === 'function') {
|
|
returnValue = this.get('oncancel').call(this, closeEvent);
|
|
if (typeof returnValue !== 'undefined') {
|
|
closeEvent.cancel = !returnValue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
autoOk: function (duration) {
|
|
startAutoConfirm(this, 0, duration);
|
|
return this;
|
|
},
|
|
autoCancel: function (duration) {
|
|
startAutoConfirm(this, 1, duration);
|
|
return this;
|
|
}
|
|
};
|
|
});
|
|
/**
|
|
* Prompt dialog object
|
|
*
|
|
* invoked by:
|
|
* alertify.prompt(message);
|
|
* alertify.prompt(message, value);
|
|
* alertify.prompt(message, value, onok);
|
|
* alertify.prompt(message, value, onok, oncancel);
|
|
* alertify.prompt(title, message, value, onok, oncancel);
|
|
*/
|
|
alertify.dialog('prompt', function () {
|
|
var input = document.createElement('INPUT');
|
|
var p = document.createElement('P');
|
|
return {
|
|
main: function (_title, _message, _value, _onok, _oncancel) {
|
|
var title, message, value, onok, oncancel;
|
|
switch (arguments.length) {
|
|
case 1:
|
|
message = _title;
|
|
break;
|
|
case 2:
|
|
message = _title;
|
|
value = _message;
|
|
break;
|
|
case 3:
|
|
message = _title;
|
|
value = _message;
|
|
onok = _value;
|
|
break;
|
|
case 4:
|
|
message = _title;
|
|
value = _message;
|
|
onok = _value;
|
|
oncancel = _onok;
|
|
break;
|
|
case 5:
|
|
title = _title;
|
|
message = _message;
|
|
value = _value;
|
|
onok = _onok;
|
|
oncancel = _oncancel;
|
|
break;
|
|
}
|
|
this.set('title', title);
|
|
this.set('message', message);
|
|
this.set('value', value);
|
|
this.set('onok', onok);
|
|
this.set('oncancel', oncancel);
|
|
return this;
|
|
},
|
|
setup: function () {
|
|
return {
|
|
buttons: [
|
|
{
|
|
text: alertify.defaults.glossary.ok,
|
|
key: keys.ENTER,
|
|
className: alertify.defaults.theme.ok,
|
|
},
|
|
{
|
|
text: alertify.defaults.glossary.cancel,
|
|
key: keys.ESC,
|
|
invokeOnClose: true,
|
|
className: alertify.defaults.theme.cancel,
|
|
}
|
|
],
|
|
focus: {
|
|
element: input,
|
|
select: true
|
|
},
|
|
options: {
|
|
maximizable: false,
|
|
resizable: false
|
|
}
|
|
};
|
|
},
|
|
build: function () {
|
|
input.className = alertify.defaults.theme.input;
|
|
input.setAttribute('type', 'text');
|
|
input.value = this.get('value');
|
|
this.elements.content.appendChild(p);
|
|
this.elements.content.appendChild(input);
|
|
},
|
|
prepare: function () {
|
|
//nothing
|
|
},
|
|
setMessage: function (message) {
|
|
if (typeof message === 'string') {
|
|
clearContents(p);
|
|
p.innerHTML = message;
|
|
} else if (message instanceof window.HTMLElement && p.firstChild !== message) {
|
|
clearContents(p);
|
|
p.appendChild(message);
|
|
}
|
|
},
|
|
settings: {
|
|
message: undefined,
|
|
labels: undefined,
|
|
onok: undefined,
|
|
oncancel: undefined,
|
|
value: '',
|
|
type:'text',
|
|
reverseButtons: undefined,
|
|
},
|
|
settingUpdated: function (key, oldValue, newValue) {
|
|
switch (key) {
|
|
case 'message':
|
|
this.setMessage(newValue);
|
|
break;
|
|
case 'value':
|
|
input.value = newValue;
|
|
break;
|
|
case 'type':
|
|
switch (newValue) {
|
|
case 'text':
|
|
case 'color':
|
|
case 'date':
|
|
case 'datetime-local':
|
|
case 'email':
|
|
case 'month':
|
|
case 'number':
|
|
case 'password':
|
|
case 'search':
|
|
case 'tel':
|
|
case 'time':
|
|
case 'week':
|
|
input.type = newValue;
|
|
break;
|
|
default:
|
|
input.type = 'text';
|
|
break;
|
|
}
|
|
break;
|
|
case 'labels':
|
|
if (newValue.ok && this.__internal.buttons[0].element) {
|
|
this.__internal.buttons[0].element.innerHTML = newValue.ok;
|
|
}
|
|
if (newValue.cancel && this.__internal.buttons[1].element) {
|
|
this.__internal.buttons[1].element.innerHTML = newValue.cancel;
|
|
}
|
|
break;
|
|
case 'reverseButtons':
|
|
if (newValue === true) {
|
|
this.elements.buttons.primary.appendChild(this.__internal.buttons[0].element);
|
|
} else {
|
|
this.elements.buttons.primary.appendChild(this.__internal.buttons[1].element);
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
callback: function (closeEvent) {
|
|
var returnValue;
|
|
switch (closeEvent.index) {
|
|
case 0:
|
|
this.settings.value = input.value;
|
|
if (typeof this.get('onok') === 'function') {
|
|
returnValue = this.get('onok').call(this, closeEvent, this.settings.value);
|
|
if (typeof returnValue !== 'undefined') {
|
|
closeEvent.cancel = !returnValue;
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
if (typeof this.get('oncancel') === 'function') {
|
|
returnValue = this.get('oncancel').call(this, closeEvent);
|
|
if (typeof returnValue !== 'undefined') {
|
|
closeEvent.cancel = !returnValue;
|
|
}
|
|
}
|
|
if(!closeEvent.cancel){
|
|
input.value = this.settings.value;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
// CommonJS
|
|
if ( typeof module === 'object' && typeof module.exports === 'object' ) {
|
|
module.exports = alertify;
|
|
// AMD
|
|
} else if ( typeof define === 'function' && define.amd) {
|
|
define( [], function () {
|
|
return alertify;
|
|
} );
|
|
// window
|
|
} else if ( !window.alertify ) {
|
|
window.alertify = alertify;
|
|
}
|
|
|
|
} ( typeof window !== 'undefined' ? window : this ) );
|