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));