1/** 2 * alertify 3 * An unobtrusive customizable JavaScript notification system 4 * 5 * @author Fabien Doiron <[email protected]> 6 * @copyright Fabien Doiron 2013 7 * @license MIT <http://opensource.org/licenses/mit-license.php> 8 * @link http://fabien-d.github.com/alertify.js/ 9 * @module alertify 10 * @version 0.3.11 11 */ 12(function (global, undefined) { 13 "use strict"; 14 var document = global.document, 15 Filertify; 16 Filertify = function () { 17 var _filertify = {}, 18 dialogs = {}, 19 isopen = false, 20 keys = { ENTER: 13, ESC: 27, SPACE: 32 }, 21 queue = [], 22 $, btnCancel, btnOK, btnReset, btnResetBack, btnFocus, elCallee, elCover, elDialog, elLog, form, input, getTransitionEvent; 23 /** 24 * Markup pieces 25 * @type {Object} 26 */ 27 dialogs = { 28 buttons : { 29 holder : "<nav class=\"filertify-buttons\">{{buttons}}</nav>", 30 submit : "<button type=\"submit\" class=\"filertify-button filertify-button-ok\" id=\"filertify-ok\">{{ok}}</button>", 31 ok : "<button class=\"filertify-button filertify-button-ok\" id=\"filertify-ok\">{{ok}}</button>", 32 cancel : "<button class=\"filertify-button filertify-button-cancel\" id=\"filertify-cancel\">{{cancel}}</button>" 33 }, 34 input : "<div class=\"filertify-text-wrapper\"><input type=\"text\" class=\"filertify-text\" id=\"filertify-text\"></div>", 35 message : "<p class=\"filertify-message\">{{message}}</p>", 36 log : "<article class=\"filertify-log{{class}}\">{{message}}</article>" 37 }; 38 /** 39 * Return the proper transitionend event 40 * @return {String} Transition type string 41 */ 42 getTransitionEvent = function () { 43 var t, 44 type, 45 supported = false, 46 el = document.createElement("fakeelement"), 47 transitions = { 48 "WebkitTransition" : "webkitTransitionEnd", 49 "MozTransition" : "transitionend", 50 "OTransition" : "otransitionend", 51 "transition" : "transitionend" 52 }; 53 for (t in transitions) { 54 if (el.style[t] !== undefined) { 55 type = transitions[t]; 56 supported = true; 57 break; 58 } 59 } 60 return { 61 type : type, 62 supported : supported 63 }; 64 }; 65 /** 66 * Shorthand for document.getElementById() 67 * 68 * @param {String} id A specific element ID 69 * @return {Object} HTML element 70 */ 71 $ = function (id) { 72 return document.getElementById(id) !== 'undefined' && document.getElementById(id) != undefined ? 73 document.getElementById(id) : document.getElementById(id); 74 }; 75 /** 76 * Filertify private object 77 * @type {Object} 78 */ 79 _filertify = { 80 /** 81 * Labels object 82 * @type {Object} 83 */ 84 labels : { 85 ok : "OK", 86 cancel : "Cancel" 87 }, 88 /** 89 * Delay number 90 * @type {Number} 91 */ 92 delay : 5000, 93 /** 94 * Whether buttons are reversed (default is secondary/primary) 95 * @type {Boolean} 96 */ 97 buttonReverse : false, 98 /** 99 * Which button should be focused by default 100 * @type {String} "ok" (default), "cancel", or "none" 101 */ 102 buttonFocus : "ok", 103 /** 104 * Set the transition event on load 105 * @type {[type]} 106 */ 107 transition : undefined, 108 /** 109 * Set the proper button click events 110 * 111 * @param {Function} fn [Optional] Callback function 112 * 113 * @return {undefined} 114 */ 115 addListeners : function (fn) { 116 var hasOK = (typeof btnOK !== "undefined"), 117 hasCancel = (typeof btnCancel !== "undefined"), 118 hasInput = (typeof input !== "undefined"), 119 val = "", 120 self = this, 121 ok, cancel, common, key, reset; 122 // ok event handler 123 ok = function (event) { 124 if (typeof event.preventDefault !== "undefined") event.preventDefault(); 125 common(event); 126 if (typeof input !== "undefined") val = input.value; 127 if (typeof fn === "function") { 128 if (typeof input !== "undefined") { 129 fn(true, val); 130 } 131 else fn(true); 132 } 133 return false; 134 }; 135 // cancel event handler 136 cancel = function (event) { 137 if (typeof event.preventDefault !== "undefined") event.preventDefault(); 138 common(event); 139 if (typeof fn === "function") fn(false); 140 return false; 141 }; 142 // common event handler (keyup, ok and cancel) 143 common = function (event) { 144 self.hide(); 145 self.unbind(document.body, "keyup", key);
146 self.unbind(btnReset, "focus", reset); 147 if (hasOK) self.unbind(btnOK, "click", ok); 148 if (hasCancel) self.unbind(btnCancel, "click", cancel); 149 }; 150 // keyup handler 151 key = function (event) { 152 var keyCode = event.keyCode; 153 if ((keyCode === keys.SPACE && !hasInput) || (hasInput && keyCode === keys.ENTER)) ok(event); 154 if (keyCode === keys.ESC && hasCancel) cancel(event); 155 }; 156 // reset focus to first item in the dialog 157 reset = function (event) { 158 if (hasInput) input.focus(); 159 else if (!hasCancel || self.buttonReverse) btnOK.focus(); 160 else btnCancel.focus(); 161 }; 162 // handle reset focus link 163 // this ensures that the keyboard focus does not 164 // ever leave the dialog box until an action has 165 // been taken 166 this.bind(btnReset, "focus", reset); 167 this.bind(btnResetBack, "focus", reset); 168 // handle OK click 169 if (hasOK) this.bind(btnOK, "click", ok); 170 // handle Cancel click 171 if (hasCancel) this.bind(btnCancel, "click", cancel); 172 // listen for keys, Cancel => ESC 173 this.bind(document.body, "keyup", key); 174 if (!this.transition.supported) { 175 this.setFocus(); 176 } 177 }, 178 /** 179 * Bind events to elements 180 * 181 * @param {Object} el HTML Object 182 * @param {Event} event Event to attach to element 183 * @param {Function} fn Callback function 184 * 185 * @return {undefined} 186 */ 187 bind : function (el, event, fn) { 188 if (typeof el.addEventListener === "function") { 189 el.addEventListener(event, fn, false); 190 } else if (el.attachEvent) { 191 el.attachEvent("on" + event, fn); 192 } 193 }, 194 /** 195 * Use filertify as the global error handler (using window.onerror) 196 * 197 * @return {boolean} success 198 */ 199 handleErrors : function () { 200 if (typeof global.onerror !== "undefined") { 201 var self = this; 202 global.onerror = function (msg, url, line) { 203 self.error("[" + msg + " on line " + line + " of " + url + "]", 0); 204 }; 205 return true; 206 } else { 207 return false; 208 } 209 }, 210 /** 211 * Append button HTML strings 212 * 213 * @param {String} secondary The secondary button HTML string 214 * @param {String} primary The primary button HTML string 215 * 216 * @return {String} The appended button HTML strings 217 */ 218 appendButtons : function (secondary, primary) { 219 return this.buttonReverse ? primary + secondary : secondary + primary; 220 }, 221 /** 222 * Build the proper message box 223 * 224 * @param {Object} item Current object in the queue 225 * 226 * @return {String} An HTML string of the message box 227 */ 228 build : function (item) { 229 var html = "", 230 type = item.type, 231 message = item.message, 232 css = item.cssClass || ""; 233 html += "<div class=\"filertify-dialog\">"; 234 html += "<a id=\"filertify-resetFocusBack\" class=\"filertify-resetFocus\" href=\"#\">Reset Focus</a>"; 235 if (_filertify.buttonFocus === "none") html += "<a href=\"#\" id=\"filertify-noneFocus\" class=\"filertify-hidden\"></a>"; 236 // doens't require an actual form 237 if (type === "prompt") html += "<div id=\"filertify-form\">"; 238 html += "<article class=\"filertify-inner\">"; 239 html += dialogs.message.replace("{{message}}", message); 240 if (type === "prompt") html += dialogs.input; 241 html += dialogs.buttons.holder; 242 html += "</article>"; 243 if (type === "prompt") html += "</div>"; 244 html += "<a id=\"filertify-resetFocus\" class=\"filertify-resetFocus\" href=\"#\">Reset Focus</a>"; 245 html += "</div>"; 246 switch (type) { 247 case "confirm": 248 html = html.replace("{{buttons}}", this.appendButtons(dialogs.buttons.cancel, dialogs.buttons.ok)); 249 html = html.replace("{{ok}}", this.labels.ok).replace("{{cancel}}", this.labels.cancel); 250 break; 251 case "prompt": 252 html = html.replace("{{buttons}}", this.appendButtons(dialogs.buttons.cancel, dialogs.buttons.submit)); 253 html = html.replace("{{ok}}", this.labels.ok).replace("{{cancel}}", this.labels.cancel); 254 break; 255 case "alert": 256 html = html.replace("{{buttons}}", dialogs.buttons.ok); 257 html = html.replace("{{ok}}", this.labels.ok); 258 break; 259 default: 260 break; 261 } 262 elDialog.className = "filertify filertify-" + type + " " + css; 263 elCover.className = "filertify-cover"; 264 return html; 265 }, 266 /** 267 * Close the log messages 268 * 269 * @param {Object} elem HTML Element of log message to close 270 * @param {Number} wait [optional] Time (in ms) to wait before automatically hiding the message, if 0 never hide 271 * 272 * @return {undefined} 273 */ 274 close : function (elem, wait) { 275 // Unary Plus: +"2" === 2 276 var timer = (wait && !isNaN(wait)) ? +wait : this.delay, 277 self = this, 278 hideElement, transitionDone; 279 // set click event on log messages 280 this.bind(elem, "click", function () { 281 hideElement(elem); 282 }); 283 // Hide the dialog box after transition 284 // This ensure it doens't block any element from being clicked 285 transitionDone = function (event) { 286 event.stopPropagation(); 287 // unbind event so function only gets called once 288 self.unbind(this, self.transition.type, transitionDone); 289 // remove log message 290 elLog.removeChild(this); 291 if (!elLog.hasChildNodes()) elLog.className += " filertify-logs-hidden"; 292 }; 293 // this sets the hide class to transition out 294 // or removes the child if css transitions aren't supported 295 hideElement = function (el) { 296 // ensure element exists 297 if (typeof el !== "undefined" && el.parentNode === elLog) { 298 // whether CSS transition exists 299 if (self.transition.supported) { 300 self.bind(el, self.transition.type, transitionDone); 301 el.className += " filertify-log-hide"; 302 } else { 303 elLog.removeChild(el); 304 if (!elLog.hasChildNodes()) elLog.className += " filertify-logs-hidden"; 305 } 306 } 307 }; 308 // never close (until click) if wait is set to 0 309 if (wait === 0) return; 310 // set timeout to auto close the log message 311 setTimeout(function () { hideElement(elem); }, timer); 312 }, 313 /** 314 * Create a dialog box 315 * 316 * @param {String} message The message passed from the callee 317 * @param {String} type Type of dialog to create 318 * @param {Function} fn [Optional] Callback function 319 * @param {String} placeholder [Optional] Default value for prompt input field 320 * @param {String} cssClass [Optional] Class(es) to append to dialog box 321 * 322 * @return {Object} 323 */ 324 dialog : function (message, type, fn, placeholder, cssClass) { 325 // set the current active element 326 // this allows the keyboard focus to be resetted 327 // after the dialog box is closed 328 elCallee = document.activeElement; 329 // check to ensure the filertify dialog element 330 // has been successfully created 331 var check = function () { 332 if ((elLog && elLog.scrollTop !== null) && (elCover && elCover.scrollTop !== null)) return; 333 else setTimeout(check, 2500); 334 }; 335 // error catching 336 if (typeof message !== "string") throw new Error("message must be a string"); 337 if (typeof type !== "string") throw new Error("type must be a string"); 338 if (typeof fn !== "undefined" && typeof fn !== "function") throw new Error("fn must be a function"); 339 // initialize filertify if it hasn't already been done 340 this.init(); 341 check(); 342 queue.push({ type: type, message: message, callback: fn, placeholder: placeholder, cssClass: cssClass }); 343 if (!isopen) this.setup(); 344 return this; 345 }, 346 /** 347 * Extend the log method to create custom methods 348 * 349 * @param {String}
349 type Custom method name 350 * 351 * @return {Function} 352 */ 353 extend : function (type) { 354 if (typeof type !== "string") throw new Error("extend method must have exactly one paramter"); 355 return function (message, wait) { 356 this.log(message, type, wait); 357 return this; 358 }; 359 }, 360 /** 361 * Hide the dialog and rest to defaults 362 * 363 * @return {undefined} 364 */ 365 hide : function () { 366 var transitionDone, 367 self = this; 368 // remove reference from queue 369 queue.splice(0,1); 370 // if items remaining in the queue 371 if (queue.length > 0) this.setup(true); 372 else { 373 isopen = false; 374 // Hide the dialog box after transition 375 // This ensure it doens't block any element from being clicked 376 transitionDone = function (event) { 377 event.stopPropagation(); 378 // unbind event so function only gets called once 379 self.unbind(elDialog, self.transition.type, transitionDone); 380 }; 381 // whether CSS transition exists 382 if (this.transition.supported) { 383 this.bind(elDialog, this.transition.type, transitionDone); 384 elDialog.className = "filertify filertify-hide filertify-hidden"; 385 } else { 386 elDialog.className = "filertify filertify-hide filertify-hidden filertify-isHidden"; 387 } 388 elCover.className = "filertify-cover filertify-cover-hidden"; 389 // set focus to the last element or body 390 // after the dialog is closed 391 elCallee.focus(); 392 } 393 }, 394 /** 395 * Initialize Filertify 396 * Create the 2 main elements 397 * 398 * @return {undefined} 399 */ 400 init : function () { 401 // ensure legacy browsers support html5 tags 402 document.createElement("nav"); 403 document.createElement("article"); 404 document.createElement("section"); 405 // cover 406 if ($("filertify-cover") == null) { 407 elCover = document.createElement("div"); 408 elCover.setAttribute("id", "filertify-cover"); 409 elCover.className = "filertify-cover filertify-cover-hidden"; 410 document.body.appendChild(elCover); 411 } 412 // main element 413 if ($("filertify") == null) { 414 isopen = false; 415 queue = []; 416 elDialog = document.createElement("section"); 417 elDialog.setAttribute("id", "filertify"); 418 elDialog.className = "filertify filertify-hidden"; 419 document.body.appendChild(elDialog); 420 } 421 // log element 422 if ($("filertify-logs") == null) { 423 elLog = document.createElement("section"); 424 elLog.setAttribute("id", "filertify-logs"); 425 elLog.className = "filertify-logs filertify-logs-hidden"; 426 document.body.appendChild(elLog); 427 } 428 // set tabindex attribute on body element 429 // this allows script to give it focus 430 // after the dialog is closed 431 document.body.setAttribute("tabindex", "0"); 432 // set transition type 433 this.transition = getTransitionEvent(); 434 }, 435 /** 436 * Show a new log message box 437 * 438 * @param {String} message The message passed from the callee 439 * @param {String} type [Optional] Optional type of log message 440 * @param {Number} wait [Optional] Time (in ms) to wait before auto-hiding the log 441 * 442 * @return {Object} 443 */ 444 log : function (message, type, wait) { 445 // check to ensure the filertify dialog element 446 // has been successfully created 447 var check = function () { 448 if (elLog && elLog.scrollTop !== null) return; 449 else setTimeout(check, 2500); 450 }; 451 // initialize filertify if it hasn't already been done 452 this.init(); 453 check(); 454 elLog.className = "filertify-logs"; 455 this.notify(message, type, wait); 456 return this; 457 }, 458 /** 459 * Add new log message 460 * If a type is passed, a class name "filertify-log-{type}" will get added. 461 * This allows for custom look and feel for various types of notifications. 462 * 463 * @param {String} message The message passed from the callee 464 * @param {String} type [Optional] Type of log message 465 * @param {Number} wait [Optional] Time (in ms) to wait before auto-hiding 466 * 467 * @return {undefined} 468 */ 469 notify : function (message, type, wait) { 470 var log = document.createElement("article"); 471 log.className = "filertify-log" + ((typeof type === "string" && type !== "") ? " filertify-log-" + type : ""); 472 log.innerHTML = message; 473 // append child 474 elLog.appendChild(log); 475 // triggers the CSS animation 476 setTimeout(function() { log.className = log.className + " filertify-log-show"; }, 50); 477 this.close(log, wait); 478 }, 479 /** 480 * Set properties 481 * 482 * @param {Object} args Passing parameters 483 * 484 * @return {undefined} 485 */ 486 set : function (args) { 487 var k; 488 // error catching 489 if (typeof args !== "object" && args instanceof Array) throw new Error("args must be an object"); 490 // set parameters 491 for (k in args) { 492 if (args.hasOwnProperty(k)) { 493 this[k] = args[k]; 494 } 495 } 496 }, 497 /** 498 * Common place to set focus to proper element 499 * 500 * @return {undefined} 501 */ 502 setFocus : function () { 503 if (input) { 504 input.focus(); 505 input.select(); 506 } 507 else btnFocus.focus(); 508 }, 509 /** 510 * Initiate all the required pieces for the dialog box 511 * 512 * @return {undefined} 513 */ 514 setup : function (fromQueue) { 515 var item = queue[0], 516 self = this, 517 transitionDone; 518 // dialog is open 519 isopen = true; 520 // Set button focus after transition 521 transitionDone = function (event) { 522 event.stopPropagation(); 523 self.setFocus(); 524 // unbind event so function only gets called once 525 self.unbind(elDialog, self.transition.type, transitionDone); 526 }; 527 // whether CSS transition exists 528 if (this.transition.supported && !fromQueue) { 529 this.bind(elDialog, this.transition.type, transitionDone); 530 } 531 // build the proper dialog HTML 532 elDialog.innerHTML = this.build(item); 533 // assign all the common elements 534 btnReset = $("filertify-resetFocus"); 535 btnResetBack = $("filertify-resetFocusBack"); 536 btnOK = $("filertify-ok") || undefined; 537 btnCancel = $("filertify-cancel") || undefined;
538 btnFocus = (_filertify.buttonFocus === "cancel") ? btnCancel : ((_filertify.buttonFocus === "none") ? $("filertify-noneFocus") : btnOK), 539 input = $("filertify-text") || undefined; 540 form = $("filertify-form") || undefined; 541 // add placeholder value to the input field 542 if (typeof item.placeholder === "string" && item.placeholder !== "") input.value = item.placeholder; 543 if (fromQueue) this.setFocus(); 544 this.addListeners(item.callback); 545 }, 546 /** 547 * Unbind events to elements 548 * 549 * @param {Object} el HTML Object 550 * @param {Event} event Event to detach to element 551 * @param {Function} fn Callback function 552 * 553 * @return {undefined} 554 */ 555 unbind : function (el, event, fn) { 556 if (typeof el.removeEventListener === "function") { 557 el.removeEventListener(event, fn, false); 558 } else if (el.detachEvent) { 559 el.detachEvent("on" + event, fn); 560 } 561 } 562 }; 563 return { 564 alert : function (message, fn, cssClass) { _filertify.dialog(message, "alert", fn, "", cssClass); return this; }, 565 confirm : function (message, fn, cssClass) { _filertify.dialog(message, "confirm", fn, "", cssClass); return this; }, 566 extend : _filertify.extend, 567 init : _filertify.init, 568 log : function (message, type, wait) { _filertify.log(message, type, wait); return this; }, 569 prompt : function (message, fn, placeholder, cssClass) { _filertify.dialog(message, "prompt", fn, placeholder, cssClass); return this; }, 570 success : function (message, wait) { _filertify.log(message, "success", wait); return this; }, 571 error : function (message, wait) { _filertify.log(message, "error", wait); return this; }, 572 set : function (args) { _filertify.set(args); }, 573 labels : _filertify.labels, 574 debug : _filertify.handleErrors 575 }; 576 }; 577 // AMD and window support 578 if (typeof define === "function") { 579 define([], function () { return new Filertify(); }); 580 } else if (typeof global.filertify === "undefined") { 581 global.filertify = new Filertify(); 582 } 583}(this));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.