| /*global define*/ |
| (function (global, undefined) { |
| "use strict"; |
| |
| var document = global.document, |
| Alertify; |
| |
| Alertify = function () { |
| |
| var _alertify = {}, |
| dialogs = {}, |
| isopen = false, |
| keys = { ENTER: 13, ESC: 27, SPACE: 32 }, |
| queue = [], |
| $, btnCancel, btnOK, btnReset, btnResetBack, btnFocus, elCallee, elCover, elDialog, elLog, form, input, getTransitionEvent; |
| |
| /** |
| * Markup pieces |
| * @type {Object} |
| */ |
| dialogs = { |
| buttons : { |
| holder : "<nav class=\"alertify-buttons\">{{buttons}}</nav>", |
| submit : "<button type=\"submit\" class=\"alertify-button alertify-button-ok\" id=\"alertify-ok\">{{ok}}</button>", |
| ok : "<button class=\"alertify-button alertify-button-ok\" id=\"alertify-ok\">{{ok}}</button>", |
| cancel : "<button class=\"alertify-button alertify-button-cancel\" id=\"alertify-cancel\">{{cancel}}</button>" |
| }, |
| input : "<div class=\"alertify-text-wrapper\"><input type=\"text\" class=\"alertify-text\" id=\"alertify-text\"></div>", |
| message : "<p class=\"alertify-message\">{{message}}</p>", |
| log : "<article class=\"alertify-log{{class}}\">{{message}}</article>" |
| }; |
| |
| /** |
| * Return the proper transitionend event |
| * @return {String} Transition type string |
| */ |
| getTransitionEvent = function () { |
| var t, |
| type, |
| supported = false, |
| el = document.createElement("fakeelement"), |
| transitions = { |
| "WebkitTransition" : "webkitTransitionEnd", |
| "MozTransition" : "transitionend", |
| "OTransition" : "otransitionend", |
| "transition" : "transitionend" |
| }; |
| |
| for (t in transitions) { |
| if (el.style[t] !== undefined) { |
| type = transitions[t]; |
| supported = true; |
| break; |
| } |
| } |
| |
| return { |
| type : type, |
| supported : supported |
| }; |
| }; |
| |
| /** |
| * Shorthand for document.getElementById() |
| * |
| * @param {String} id A specific element ID |
| * @return {Object} HTML element |
| */ |
| $ = function (id) { |
| return document.getElementById(id); |
| }; |
| |
| /** |
| * Alertify private object |
| * @type {Object} |
| */ |
| _alertify = { |
| |
| /** |
| * Labels object |
| * @type {Object} |
| */ |
| labels : { |
| ok : "OK", |
| cancel : "Cancel" |
| }, |
| |
| /** |
| * Delay number |
| * @type {Number} |
| */ |
| delay : 5000, |
| |
| /** |
| * Whether buttons are reversed (default is secondary/primary) |
| * @type {Boolean} |
| */ |
| buttonReverse : false, |
| |
| /** |
| * Which button should be focused by default |
| * @type {String} "ok" (default), "cancel", or "none" |
| */ |
| buttonFocus : "ok", |
| |
| /** |
| * Set the transition event on load |
| * @type {[type]} |
| */ |
| transition : undefined, |
| |
| /** |
| * Set the proper button click events |
| * |
| * @param {Function} fn [Optional] Callback function |
| * |
| * @return {undefined} |
| */ |
| addListeners : function (fn) { |
| var hasOK = (typeof btnOK !== "undefined"), |
| hasCancel = (typeof btnCancel !== "undefined"), |
| hasInput = (typeof input !== "undefined"), |
| val = "", |
| self = this, |
| ok, cancel, common, key, reset; |
| |
| // ok event handler |
| ok = function (event) { |
| if (typeof event.preventDefault !== "undefined") event.preventDefault(); |
| common(event); |
| if (typeof input !== "undefined") val = input.value; |
| if (typeof fn === "function") { |
| if (typeof input !== "undefined") { |
| fn(true, val); |
| } |
| else fn(true); |
| } |
| return false; |
| }; |
| |
| // cancel event handler |
| cancel = function (event) { |
| if (typeof event.preventDefault !== "undefined") event.preventDefault(); |
| common(event); |
| if (typeof fn === "function") fn(false); |
| return false; |
| }; |
| |
| // common event handler (keyup, ok and cancel) |
| common = function (event) { |
| self.hide(); |
| self.unbind(document.body, "keyup", key); |
| self.unbind(btnReset, "focus", reset); |
| if (hasOK) self.unbind(btnOK, "click", ok); |
| if (hasCancel) self.unbind(btnCancel, "click", cancel); |
| }; |
| |
| // keyup handler |
| key = function (event) { |
| var keyCode = event.keyCode; |
| if ((keyCode === keys.SPACE && !hasInput) || (hasInput && keyCode === keys.ENTER)) ok(event); |
| if (keyCode === keys.ESC && hasCancel) cancel(event); |
| }; |
| |
| // reset focus to first item in the dialog |
| reset = function (event) { |
| if (hasInput) input.focus(); |
| else if (!hasCancel || self.buttonReverse) btnOK.focus(); |
| else btnCancel.focus(); |
| }; |
| |
| // handle reset focus link |
| // this ensures that the keyboard focus does not |
| // ever leave the dialog box until an action has |
| // been taken |
| this.bind(btnReset, "focus", reset); |
| this.bind(btnResetBack, "focus", reset); |
| // handle OK click |
| if (hasOK) this.bind(btnOK, "click", ok); |
| // handle Cancel click |
| if (hasCancel) this.bind(btnCancel, "click", cancel); |
| // listen for keys, Cancel => ESC |
| this.bind(document.body, "keyup", key); |
| if (!this.transition.supported) { |
| this.setFocus(); |
| } |
| }, |
| |
| /** |
| * Bind events to elements |
| * |
| * @param {Object} el HTML Object |
| * @param {Event} event Event to attach to element |
| * @param {Function} fn Callback function |
| * |
| * @return {undefined} |
| */ |
| bind : function (el, event, fn) { |
| if (typeof el.addEventListener === "function") { |
| el.addEventListener(event, fn, false); |
| } else if (el.attachEvent) { |
| el.attachEvent("on" + event, fn); |
| } |
| }, |
| |
| /** |
| * Use alertify as the global error handler (using window.onerror) |
| * |
| * @return {boolean} success |
| */ |
| handleErrors : function () { |
| if (typeof global.onerror !== "undefined") { |
| var self = this; |
| global.onerror = function (msg, url, line) { |
| self.error("[" + msg + " on line " + line + " of " + url + "]", 0); |
| }; |
| return true; |
| } else { |
| return false; |
| } |
| }, |
| |
| /** |
| * Append button HTML strings |
| * |
| * @param {String} secondary The secondary button HTML string |
| * @param {String} primary The primary button HTML string |
| * |
| * @return {String} The appended button HTML strings |
| */ |
| appendButtons : function (secondary, primary) { |
| return this.buttonReverse ? primary + secondary : secondary + primary; |
| }, |
| |
| /** |
| * Build the proper message box |
| * |
| * @param {Object} item Current object in the queue |
| * |
| * @return {String} An HTML string of the message box |
| */ |
| build : function (item) { |
| var html = "", |
| type = item.type, |
| message = item.message, |
| css = item.cssClass || ""; |
| |
| html += "<div class=\"alertify-dialog\">"; |
| html += "<a id=\"alertify-resetFocusBack\" class=\"alertify-resetFocus\" href=\"#\">Reset Focus</a>"; |
| |
| if (_alertify.buttonFocus === "none") html += "<a href=\"#\" id=\"alertify-noneFocus\" class=\"alertify-hidden\"></a>"; |
| |
| // doens't require an actual form |
| if (type === "prompt") html += "<div id=\"alertify-form\">"; |
| |
| html += "<article class=\"alertify-inner\">"; |
| html += dialogs.message.replace("{{message}}", message); |
| |
| if (type === "prompt") html += dialogs.input; |
| |
| html += dialogs.buttons.holder; |
| html += "</article>"; |
| |
| if (type === "prompt") html += "</div>"; |
| |
| html += "<a id=\"alertify-resetFocus\" class=\"alertify-resetFocus\" href=\"#\">Reset Focus</a>"; |
| html += "</div>"; |
| |
| switch (type) { |
| case "confirm": |
| html = html.replace("{{buttons}}", this.appendButtons(dialogs.buttons.cancel, dialogs.buttons.ok)); |
| html = html.replace("{{ok}}", this.labels.ok).replace("{{cancel}}", this.labels.cancel); |
| break; |
| case "prompt": |
| html = html.replace("{{buttons}}", this.appendButtons(dialogs.buttons.cancel, dialogs.buttons.submit)); |
| html = html.replace("{{ok}}", this.labels.ok).replace("{{cancel}}", this.labels.cancel); |
| break; |
| case "alert": |
| html = html.replace("{{buttons}}", dialogs.buttons.ok); |
| html = html.replace("{{ok}}", this.labels.ok); |
| break; |
| default: |
| break; |
| } |
| |
| elDialog.className = "alertify alertify-" + type + " " + css; |
| elCover.className = "alertify-cover"; |
| return html; |
| }, |
| |
| /** |
| * Close the log messages |
| * |
| * @param {Object} elem HTML Element of log message to close |
| * @param {Number} wait [optional] Time (in ms) to wait before automatically hiding the message, if 0 never hide |
| * |
| * @return {undefined} |
| */ |
| close : function (elem, wait) { |
| // Unary Plus: +"2" === 2 |
| var timer = (wait && !isNaN(wait)) ? +wait : this.delay, |
| self = this, |
| hideElement, transitionDone; |
| |
| // set click event on log messages |
| this.bind(elem, "click", function () { |
| hideElement(elem); |
| }); |
| // Hide the dialog box after transition |
| // This ensure it doens't block any element from being clicked |
| transitionDone = function (event) { |
| event.stopPropagation(); |
| // unbind event so function only gets called once |
| self.unbind(this, self.transition.type, transitionDone); |
| // remove log message |
| elLog.removeChild(this); |
| if (!elLog.hasChildNodes()) elLog.className += " alertify-logs-hidden"; |
| }; |
| // this sets the hide class to transition out |
| // or removes the child if css transitions aren't supported |
| hideElement = function (el) { |
| // ensure element exists |
| if (typeof el !== "undefined" && el.parentNode === elLog) { |
| // whether CSS transition exists |
| if (self.transition.supported) { |
| self.bind(el, self.transition.type, transitionDone); |
| el.className += " alertify-log-hide"; |
| } else { |
| elLog.removeChild(el); |
| if (!elLog.hasChildNodes()) elLog.className += " alertify-logs-hidden"; |
| } |
| } |
| }; |
| // never close (until click) if wait is set to 0 |
| if (wait === 0) return; |
| // set timeout to auto close the log message |
| setTimeout(function () { hideElement(elem); }, timer); |
| }, |
| |
| /** |
| * Create a dialog box |
| * |
| * @param {String} message The message passed from the callee |
| * @param {String} type Type of dialog to create |
| * @param {Function} fn [Optional] Callback function |
| * @param {String} placeholder [Optional] Default value for prompt input field |
| * @param {String} cssClass [Optional] Class(es) to append to dialog box |
| * |
| * @return {Object} |
| */ |
| dialog : function (message, type, fn, placeholder, cssClass) { |
| // set the current active element |
| // this allows the keyboard focus to be resetted |
| // after the dialog box is closed |
| elCallee = document.activeElement; |
| // check to ensure the alertify dialog element |
| // has been successfully created |
| var check = function () { |
| if ((elLog && elLog.scrollTop !== null) && (elCover && elCover.scrollTop !== null)) return; |
| else check(); |
| }; |
| // error catching |
| if (typeof message !== "string") throw new Error("message must be a string"); |
| if (typeof type !== "string") throw new Error("type must be a string"); |
| if (typeof fn !== "undefined" && typeof fn !== "function") throw new Error("fn must be a function"); |
| // initialize alertify if it hasn't already been done |
| this.init(); |
| check(); |
| |
| queue.push({ type: type, message: message, callback: fn, placeholder: placeholder, cssClass: cssClass }); |
| if (!isopen) this.setup(); |
| |
| return this; |
| }, |
| |
| /** |
| * Extend the log method to create custom methods |
| * |
| * @param {String} type Custom method name |
| * |
| * @return {Function} |
| */ |
| extend : function (type) { |
| if (typeof type !== "string") throw new Error("extend method must have exactly one paramter"); |
| return function (message, wait) { |
| this.log(message, type, wait); |
| return this; |
| }; |
| }, |
| |
| /** |
| * Hide the dialog and rest to defaults |
| * |
| * @return {undefined} |
| */ |
| hide : function () { |
| var transitionDone, |
| self = this; |
| // remove reference from queue |
| queue.splice(0,1); |
| // if items remaining in the queue |
| if (queue.length > 0) this.setup(true); |
| else { |
| isopen = false; |
| // Hide the dialog box after transition |
| // This ensure it doens't block any element from being clicked |
| transitionDone = function (event) { |
| event.stopPropagation(); |
| // unbind event so function only gets called once |
| self.unbind(elDialog, self.transition.type, transitionDone); |
| }; |
| // whether CSS transition exists |
| if (this.transition.supported) { |
| this.bind(elDialog, this.transition.type, transitionDone); |
| elDialog.className = "alertify alertify-hide alertify-hidden"; |
| } else { |
| elDialog.className = "alertify alertify-hide alertify-hidden alertify-isHidden"; |
| } |
| elCover.className = "alertify-cover alertify-cover-hidden"; |
| // set focus to the last element or body |
| // after the dialog is closed |
| elCallee.focus(); |
| } |
| }, |
| |
| /** |
| * Initialize Alertify |
| * Create the 2 main elements |
| * |
| * @return {undefined} |
| */ |
| init : function () { |
| // ensure legacy browsers support html5 tags |
| document.createElement("nav"); |
| document.createElement("article"); |
| document.createElement("section"); |
| // cover |
| if ($("alertify-cover") == null) { |
| elCover = document.createElement("div"); |
| elCover.setAttribute("id", "alertify-cover"); |
| elCover.className = "alertify-cover alertify-cover-hidden"; |
| document.body.appendChild(elCover); |
| } |
| // main element |
| if ($("alertify") == null) { |
| isopen = false; |
| queue = []; |
| elDialog = document.createElement("section"); |
| elDialog.setAttribute("id", "alertify"); |
| elDialog.className = "alertify alertify-hidden"; |
| document.body.appendChild(elDialog); |
| } |
| // log element |
| if ($("alertify-logs") == null) { |
| elLog = document.createElement("section"); |
| elLog.setAttribute("id", "alertify-logs"); |
| elLog.className = "alertify-logs alertify-logs-hidden"; |
| document.body.appendChild(elLog); |
| } |
| // set tabindex attribute on body element |
| // this allows script to give it focus |
| // after the dialog is closed |
| document.body.setAttribute("tabindex", "0"); |
| // set transition type |
| this.transition = getTransitionEvent(); |
| }, |
| |
| /** |
| * Show a new log message box |
| * |
| * @param {String} message The message passed from the callee |
| * @param {String} type [Optional] Optional type of log message |
| * @param {Number} wait [Optional] Time (in ms) to wait before auto-hiding the log |
| * |
| * @return {Object} |
| */ |
| log : function (message, type, wait) { |
| // check to ensure the alertify dialog element |
| // has been successfully created |
| var check = function () { |
| if (elLog && elLog.scrollTop !== null) return; |
| else check(); |
| }; |
| // initialize alertify if it hasn't already been done |
| this.init(); |
| check(); |
| |
| elLog.className = "alertify-logs"; |
| this.notify(message, type, wait); |
| return this; |
| }, |
| |
| /** |
| * Add new log message |
| * If a type is passed, a class name "alertify-log-{type}" will get added. |
| * This allows for custom look and feel for various types of notifications. |
| * |
| * @param {String} message The message passed from the callee |
| * @param {String} type [Optional] Type of log message |
| * @param {Number} wait [Optional] Time (in ms) to wait before auto-hiding |
| * |
| * @return {undefined} |
| */ |
| notify : function (message, type, wait) { |
| var log = document.createElement("article"); |
| log.className = "alertify-log" + ((typeof type === "string" && type !== "") ? " alertify-log-" + type : ""); |
| log.innerHTML = message; |
| // append child |
| elLog.appendChild(log); |
| // triggers the CSS animation |
| setTimeout(function() { log.className = log.className + " alertify-log-show"; }, 50); |
| this.close(log, wait); |
| }, |
| |
| /** |
| * Set properties |
| * |
| * @param {Object} args Passing parameters |
| * |
| * @return {undefined} |
| */ |
| set : function (args) { |
| var k; |
| // error catching |
| if (typeof args !== "object" && args instanceof Array) throw new Error("args must be an object"); |
| // set parameters |
| for (k in args) { |
| if (args.hasOwnProperty(k)) { |
| this[k] = args[k]; |
| } |
| } |
| }, |
| |
| /** |
| * Common place to set focus to proper element |
| * |
| * @return {undefined} |
| */ |
| setFocus : function () { |
| if (input) { |
| input.focus(); |
| input.select(); |
| } |
| else btnFocus.focus(); |
| }, |
| |
| /** |
| * Initiate all the required pieces for the dialog box |
| * |
| * @return {undefined} |
| */ |
| setup : function (fromQueue) { |
| var item = queue[0], |
| self = this, |
| transitionDone; |
| |
| // dialog is open |
| isopen = true; |
| // Set button focus after transition |
| transitionDone = function (event) { |
| event.stopPropagation(); |
| self.setFocus(); |
| // unbind event so function only gets called once |
| self.unbind(elDialog, self.transition.type, transitionDone); |
| }; |
| // whether CSS transition exists |
| if (this.transition.supported && !fromQueue) { |
| this.bind(elDialog, this.transition.type, transitionDone); |
| } |
| // build the proper dialog HTML |
| elDialog.innerHTML = this.build(item); |
| // assign all the common elements |
| btnReset = $("alertify-resetFocus"); |
| btnResetBack = $("alertify-resetFocusBack"); |
| btnOK = $("alertify-ok") || undefined; |
| btnCancel = $("alertify-cancel") || undefined; |
| btnFocus = (_alertify.buttonFocus === "cancel") ? btnCancel : ((_alertify.buttonFocus === "none") ? $("alertify-noneFocus") : btnOK), |
| input = $("alertify-text") || undefined; |
| form = $("alertify-form") || undefined; |
| // add placeholder value to the input field |
| if (typeof item.placeholder === "string" && item.placeholder !== "") input.value = item.placeholder; |
| if (fromQueue) this.setFocus(); |
| this.addListeners(item.callback); |
| }, |
| |
| /** |
| * Unbind events to elements |
| * |
| * @param {Object} el HTML Object |
| * @param {Event} event Event to detach to element |
| * @param {Function} fn Callback function |
| * |
| * @return {undefined} |
| */ |
| unbind : function (el, event, fn) { |
| if (typeof el.removeEventListener === "function") { |
| el.removeEventListener(event, fn, false); |
| } else if (el.detachEvent) { |
| el.detachEvent("on" + event, fn); |
| } |
| } |
| }; |
| |
| return { |
| alert : function (message, fn, cssClass) { _alertify.dialog(message, "alert", fn, "", cssClass); return this; }, |
| confirm : function (message, fn, cssClass) { _alertify.dialog(message, "confirm", fn, "", cssClass); return this; }, |
| extend : _alertify.extend, |
| init : _alertify.init, |
| log : function (message, type, wait) { _alertify.log(message, type, wait); return this; }, |
| prompt : function (message, fn, placeholder, cssClass) { _alertify.dialog(message, "prompt", fn, placeholder, cssClass); return this; }, |
| success : function (message, wait) { _alertify.log(message, "success", wait); return this; }, |
| error : function (message, wait) { _alertify.log(message, "error", wait); return this; }, |
| set : function (args) { _alertify.set(args); }, |
| labels : _alertify.labels, |
| debug : _alertify.handleErrors |
| }; |
| }; |
| |
| // AMD and window support |
| if (typeof define === "function") { |
| define([], function () { return new Alertify(); }); |
| } else if (typeof global.alertify === "undefined") { |
| global.alertify = new Alertify(); |
| } |
| |
| }(this)); |