/** Prompt Boxes * @blab+ Choices Box * * Version 1.2.1 */ (function (name, context, definition) { 'use strict' if (typeof window.define === 'function' && window.define.amd) { window.define(definition) } else if (typeof module !== 'undefined' && module.exports) { module.exports = definition() } else if (context.exports) { context.exports = definition() } else { context[name] = definition() } })('PromptBoxes', this, function () { 'use strict' var PromptBoxes = function (options) { if (!(this instanceof PromptBoxes)) { return new PromptBoxes(options) } var defaultOptions = { attrPrefix: 'pb', // The class/id prefix for all elements speeds: { backdrop: 250, // The enter/leaving animation speed of the backdrop toasts: 250 // The enter/leaving animation speed of the toast }, alert: { okText: 'Ok', // The text for the ok button okClass: '', // A class for the ok button closeWithEscape: false, // Allow closing with escaping absolute: false // Show prompt popup as absolute }, choices: { choicesClass: '', // A class for the choices button cancelText: 'Cancel', // The text for the cancel button cancelClass: '', // A class for the cancel button closeWithEscape: true, // Allow closing with escaping absolute: false // Show prompt popup as absolute }, confirm: { confirmText: 'Confirm', // The text for the confirm button confirmClass: '', // A class for the confirm button cancelText: 'Cancel', // The text for the cancel button cancelClass: '', // A class for the cancel button closeWithEscape: true, // Allow closing with escaping absolute: false // Show prompt popup as absolute }, prompt: { inputType: 'text', // The type of input 'text' | 'password' etc. submitText: 'Submit', // The text for the submit button submitClass: '', // A class for the submit button cancelText: 'Cancel', // The text for the cancel button cancelClass: '', // A class for the cancel button closeWithEscape: true, // Allow closing with escaping absolute: false // Show prompt popup as absolute }, toasts: { direction: 'top', // Which direction to show the toast 'top' | 'bottom' max: 5, // The number of toasts that can be in the stack duration: 5000, // The time the toast appears showTimerBar: true, // Show timer bar countdown closeWithEscape: true, // Allow closing with escaping allowClose: false, // Whether to show a "x" to close the toast } } this.options = this._extend(options, defaultOptions); for (var property in this._prefixes) { if (this._prefixes.hasOwnProperty(property)) this._prefixes[property] = this.options.attrPrefix + '-' + property; } } PromptBoxes.prototype = { _extend: function (source, target) { if (source == null) { return target } for (var k in source) { if (source[k] != null && target[k] !== source[k]) { target[k] = source[k] } } return target }, _prefixes: { toast: '', confirm: '', prompt: '', alert: '', container: '', backdrop: '', message: '', buttons: '', timerBar: '' }, _destroyBase: function (elements) { var that = this; // Add hide animation classes for (var i = 0; i < elements.length; i++) elements[i].removeAttribute('data-show'); // Remove escape event from body that._addEscapeEvent(null); // Wait for animations to finish then remove from DOM setTimeout(function () { for (var i = 0; i < elements.length; i++) elements[i].remove(); }, that.options.speeds.backdrop) }, _addEscapeEvent: function (callback) { var body$ = document.getElementsByTagName('body')[0]; body$.onkeyup = !callback ? null : function (ev) { if (ev.keyCode === 27) callback(); }; }, /** * Clears all instances from view */ clear: function () { var that = this; // Remove toasts that._toastQueue = []; that._displayToasts(); // Remove backdrop/alerts/prompts/confirms var elements = []; var classes = [that._prefixes.backdrop, that._prefixes.alert, that._prefixes.confirm, that._prefixes.choices, that._prefixes.prompt]; for (var i = 0; i < classes.length; i++) { var els = document.getElementsByClassName(classes[i]); for (var j = 0; j < els.length; j++) elements.push(els[j]); } that._destroyBase(elements); }, /** * Show an alert box * * @param {*} callback the function called on complete. (outome) => { } * @param {*} msg The message to display * @param {*} okText The ok button text. If undefined it will use the options value * @param {*} opts Alert options to override */ alert: function (callback, msg, okText, opts) { var that = this; // Re-create options opts = that._extend(opts, Object.assign({}, that.options.alert)); if (!msg) msg = 'This is an alert'; if (!!okText) opts.okText = okText; // Base elements var backdrop$ = document.createElement('div'); var base$ = document.createElement('div'); base$.id = that._prefixes.container; base$.className = that._prefixes.alert; backdrop$.className = that._prefixes.backdrop; // Position base element as absolute if (opts.absolute === true) { var doc = document.documentElement; var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); base$.style.position = 'absolute'; base$.style.top = top + 'px'; } // Message elements var message$ = document.createElement('div'); message$.className = that._prefixes.message; message$.innerHTML = msg; // Buttons var buttons$ = document.createElement('div'); var ok$ = document.createElement('button'); buttons$.className = that._prefixes.buttons; ok$.className = opts.okClass; ok$.innerHTML = opts.okText; // Method to remove bases and return result var complete = function (outcome) { callback(outcome || false); that._destroyBase([backdrop$, base$]); }; ok$.onclick = function () { complete(true); } buttons$.appendChild(ok$); base$.appendChild(message$); base$.appendChild(buttons$); var body$ = document.getElementsByTagName('body')[0]; body$.appendChild(backdrop$) body$.appendChild(base$); if (opts.closeWithEscape) that._addEscapeEvent(complete); setTimeout(function () { ok$.focus(); backdrop$.setAttribute('data-show', 'true'); base$.setAttribute('data-show', 'true'); }, 50); }, /** * Show a choices dialogue with cancel and confirm actions * * @param {*} callback the function called on complete. (outome) => { } * @param {*} msg The message to display * @param {*} choices The choices button text * @param {*} cancelText The cancel button text. If undefined it will use the options value * @param {*} opts Choices options to override */ choices: function (callback, msg, choices , cancelText, opts) { var that = this; var buttonsChoices = []; // Re-create options opts = that._extend(opts, Object.assign({}, that.options.choices)); if (!msg) msg = 'Select action'; if (!!cancelText) opts.cancelText = cancelText; // Base elements var backdrop$ = document.createElement('div'); var base$ = document.createElement('div'); base$.id = that._prefixes.container; base$.className = that._prefixes.choices; backdrop$.className = that._prefixes.backdrop; // Position base element as absolute if (opts.absolute === true) { var doc = document.documentElement; var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); base$.style.position = 'absolute'; base$.style.top = top + 'px'; } // Message elements var message$ = document.createElement('div'); message$.className = that._prefixes.message; message$.innerHTML = msg; // Buttons var buttons$ = document.createElement('div'); var cancel$ = document.createElement('button'); buttons$.className = that._prefixes.buttons; cancel$.className = opts.cancelClass; cancel$.innerHTML = opts.cancelText; // Method to remove bases and return result var complete = function (outcome) { callback(outcome || false); that._destroyBase([backdrop$, base$]); }; cancel$.onclick = function () { complete(); } buttons$.appendChild(cancel$); base$.appendChild(message$); choices.forEach(function (choice) { var div = document.createElement('div'); var choice$ = document.createElement('button'); div.appendChild(choice$); div.style.margin="20px"; choice$.style.width="100%"; choice$.className = opts.confirmClass; choice$.innerHTML = choice; buttonsChoices.push(choice$) choice$.onclick = function () { complete(choice); } base$.appendChild(div); }) base$.appendChild(buttons$); var body$ = document.getElementsByTagName('body')[0]; body$.appendChild(backdrop$) body$.appendChild(base$); if (opts.closeWithEscape) that._addEscapeEvent(complete); setTimeout(function () { buttonsChoices[0].focus(); backdrop$.setAttribute('data-show', 'true'); base$.setAttribute('data-show', 'true'); }, 50); }, /** * Show a confirm dialogue with cancel and confirm actions * * @param {*} callback the function called on complete. (outome) => { } * @param {*} msg The message to display * @param {*} submitText The confirm button text. If undefined it will use the options value * @param {*} cancelText The cancel button text. If undefined it will use the options value * @param {*} opts Confirm options to override */ confirm: function (callback, msg, confirmText, cancelText, opts) { var that = this; // Re-create options opts = that._extend(opts, Object.assign({}, that.options.confirm)); if (!msg) msg = 'Please confirm this action'; if (!!confirmText) opts.confirmText = confirmText; if (!!cancelText) opts.cancelText = cancelText; // Base elements var backdrop$ = document.createElement('div'); var base$ = document.createElement('div'); base$.id = that._prefixes.container; base$.className = that._prefixes.confirm; backdrop$.className = that._prefixes.backdrop; // Position base element as absolute if (opts.absolute === true) { var doc = document.documentElement; var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); base$.style.position = 'absolute'; base$.style.top = top + 'px'; } // Message elements var message$ = document.createElement('div'); message$.className = that._prefixes.message; message$.innerHTML = msg; // Buttons var buttons$ = document.createElement('div'); var confirm$ = document.createElement('button'); var cancel$ = document.createElement('button'); buttons$.className = that._prefixes.buttons; confirm$.className = opts.confirmClass; confirm$.innerHTML = opts.confirmText; cancel$.className = opts.cancelClass; cancel$.innerHTML = opts.cancelText; // Method to remove bases and return result var complete = function (outcome) { callback(outcome || false); that._destroyBase([backdrop$, base$]); }; cancel$.onclick = function () { complete(); } confirm$.onclick = function () { complete(true); } buttons$.appendChild(cancel$); buttons$.appendChild(confirm$); base$.appendChild(message$); base$.appendChild(buttons$); var body$ = document.getElementsByTagName('body')[0]; body$.appendChild(backdrop$) body$.appendChild(base$); if (opts.closeWithEscape) that._addEscapeEvent(complete); setTimeout(function () { confirm$.focus(); backdrop$.setAttribute('data-show', 'true'); base$.setAttribute('data-show', 'true'); }, 50); }, /** * Show a prompt dialogue with an input field, cancel and submit action * * @param {*} callback the function called on complete. (value) => { } // false is return for cancel * @param {*} inputType The input type. If undefined it will use the options value * @param {*} submitText The submit button text. If undefined it will use the options value * @param {*} cancelText The cancel button text. If undefined it will use the options value * @param {*} msg The message to display * @param {*} opts Prompt options to override */ prompt: function (callback, msg, inputType, submitText, cancelText, opts) { var that = this; // Re-create options opts = that._extend(opts, Object.assign({}, that.options.prompt)); if (!msg) msg = 'Are you sure?'; if (!!inputType) opts.inputType = inputType; if (!!submitText) opts.submitText = submitText; if (!!cancelText) opts.cancelText = cancelText; // Base elements var backdrop$ = document.createElement('div'); var base$ = document.createElement('div'); base$.id = that._prefixes.container; base$.className = that._prefixes.prompt; backdrop$.className = that._prefixes.backdrop; // Position base element as absolute if (opts.absolute === true) { var doc = document.documentElement; var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); base$.style.position = 'absolute'; base$.style.top = top + 'px'; } // Message elements var message$ = document.createElement('div'); message$.className = that._prefixes.message; message$.innerHTML = msg; // Input element var input$ = document.createElement('input'); input$.type = opts.inputType; if (opts.value) input$.value = opts.value; // Buttons var buttons$ = document.createElement('div'); var submit$ = document.createElement('button'); var cancel$ = document.createElement('button'); buttons$.className = that._prefixes.buttons; submit$.className = opts.submitClass; if (!opts.value) submit$.setAttribute('disabled', 'disabled'); submit$.innerHTML = opts.submitText; cancel$.className = opts.cancelClass; cancel$.innerHTML = opts.cancelText; // Method to remove bases and return result var complete = function (value) { callback(value || false); that._destroyBase([backdrop$, base$]); }; // Add input event. Listen to enter command. input$.onkeyup = function (ev) { var val = input$.value; if (val === '') return submit$.setAttribute('disabled', 'disabled'); submit$.removeAttribute('disabled'); if (ev.keyCode !== 13) return; complete(val); } cancel$.onclick = function () { complete(); } submit$.onclick = function () { var value = input$.value; if (value === '') return input$.focus(); complete(value); } buttons$.appendChild(cancel$); buttons$.appendChild(submit$); base$.appendChild(message$); base$.appendChild(input$); base$.appendChild(buttons$); var body$ = document.getElementsByTagName('body')[0]; body$.appendChild(backdrop$) body$.appendChild(base$); if (opts.closeWithEscape) that._addEscapeEvent(complete); setTimeout(function () { input$.focus(); backdrop$.setAttribute('data-show', 'true'); base$.setAttribute('data-show', 'true'); }, 50); }, /** * * * * TOASTS * * */ _toastQueue: [], _addToast: function (el$, className, opts) { var that = this; // Remove excess toast if we're over our limit and to give room for our new toast if (that._toastQueue.length >= opts.max) { that._toastQueue.splice(opts.max - 1, (that._toastQueue.length - (opts.max - 1))); } // Add toast to queue that._toastQueue.unshift({ id: el$.id, className: className }); // Trigger animation once loaded to DOM setTimeout(function () { el$.className = className + ' show'; }, 50); }, _removeToast: function (el$) { var that = this; // Loop through the queue and remove the requested toast from the queue for (var i = 0; i < that._toastQueue.length; i++) { if (that._toastQueue[i].id === el$.id) { that._toastQueue.splice(i, 1); break; } } }, _displayToasts: function (opts) { var that = this; opts = opts || that.options.toasts; // Method that physically removes elements from the screen var destroyToast = function (el$) { el$.className = 'gone ' + el$.className; setTimeout(function () { try { el$.remove(); } catch (ex) { console.error(ex) } }, that.options.speeds.toasts); }; // Get current element list from dom var toastList = document.getElementsByClassName(that._prefixes.toast); // Loop through current queue and remove any elements that are no longer present for (var i = 0; i < toastList.length; i++) { var tId = toastList[i].id; var exists = false; for (var j = 0; j < that._toastQueue.length; j++) { if (tId === that._toastQueue[j].id) { exists = true; } } if (!exists) { destroyToast(toastList[i]); } } // Calculate margin of toasts to show var height = 0; for (var i = 0; i < that._toastQueue.length; i++) { height += 10; var el = document.getElementById(that._toastQueue[i].id); if (!el) break; if (i > 0) { var prevEl = document.getElementById(that._toastQueue[i - 1].id); if (prevEl) height += prevEl.clientHeight; } if (opts.direction === 'bottom') { el.style.marginBottom = height + 'px'; } else { el.style.marginTop = height + 'px'; } } }, /** * Shows a toast * * @param {*} msg The message to show * @param {*} stateClass The class to assign the toast 'success' | 'error' | 'info' | custom * @param {*} opts Toast options to override */ toast: function (msg, stateClass, opts) { var that = this; // Re-create options opts = that._extend(opts, Object.assign({}, that.options.toasts)); // Build base elements var className = that._prefixes.toast + ' ' + opts.direction + ' ' + (stateClass || 'info'); var base$ = document.createElement('div'); base$.innerHTML = msg; base$.id = 'toast_' + that._toastQueue.length + '_' + Math.random(); base$.className = className; // Show close option? if (opts.allowClose) { var close$ = document.createElement('a'); close$.href = "javascript:void(0)"; close$.innerHTML = '×'; close$.className = 'toast-close'; close$.onclick = function () { that._removeToast(base$); that._displayToasts(opts); }; base$.appendChild(close$); base$.setAttribute('data-close', true); } // Add to body document.getElementsByTagName('body')[0].appendChild(base$); // Run animations that._addToast(base$, className, opts); that._displayToasts(opts); // Set duration logic if (opts.duration) { if (opts.showTimerBar) { var timerBar$ = document.createElement('div'); timerBar$.style.position = 'absolute'; timerBar$.style.bottom = 0; timerBar$.style.left = 0; timerBar$.style.width = 0; timerBar$.style.height = '4px'; timerBar$.style.background = 'rgba(0, 0, 0, 0.25)'; timerBar$.style.transition = 'width linear ' + opts.duration + 'ms'; timerBar$.className = that._prefixes.timerBar; base$.appendChild(timerBar$); // Add timer countdown setTimeout(function () { timerBar$.style.width = '100%' }, 50); } // Hide toast once completed time setTimeout(function () { that._removeToast(base$); that._displayToasts(opts); }, opts.duration); } }, /** * Displays a success toast * * @param {*} msg The message to show * @param {*} opts Toast options to override */ success: function (msg, opts) { this.toast(msg, 'success', opts); }, /** * Displays a error toast * * @param {*} msg The message to show * @param {*} opts Toast options to override */ error: function (msg, opts) { this.toast(msg, 'error', opts); }, /** * Displays a info toast * * @param {*} msg The message to show * @param {*} opts Toast options to override */ info: function (msg, opts) { this.toast(msg, 'info', opts); } } return PromptBoxes }) Element.prototype.remove = function () { if (this.parentElement) this.parentElement.removeChild(this); } NodeList.prototype.remove = HTMLCollection.prototype.remove = function () { for (var i = this.length - 1; i >= 0; i--) { if (this[i] && this[i].parentElement) { this[i].parentElement.removeChild(this[i]); } } }