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