Mon 21 Jul 22:43:21 CEST 2025

This commit is contained in:
sbosse 2025-07-21 23:34:40 +02:00
parent 312c3cc4b8
commit 42d3a38186

View File

@ -0,0 +1,662 @@
/** 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 = '&times;';
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]);
}
}
}