|
@@ -0,0 +1,1189 @@
|
|
|
|
+/*! @preserve
|
|
|
|
+ * bootbox.js
|
|
|
|
+ * version: 5.3.4
|
|
|
|
+ * author: Nick Payne <nick@kurai.co.uk>
|
|
|
|
+ * license: MIT
|
|
|
|
+ * http://bootboxjs.com/
|
|
|
|
+ */
|
|
|
|
+(function (root, factory) {
|
|
|
|
+ 'use strict';
|
|
|
|
+ if (typeof define === 'function' && define.amd) {
|
|
|
|
+ // AMD
|
|
|
|
+ define(['jquery'], factory);
|
|
|
|
+ } else if (typeof exports === 'object') {
|
|
|
|
+ // Node, CommonJS-like
|
|
|
|
+ module.exports = factory(require('jquery'));
|
|
|
|
+ } else {
|
|
|
|
+ // Browser globals (root is window)
|
|
|
|
+ root.bootbox = factory(root.jQuery);
|
|
|
|
+ }
|
|
|
|
+}(this, function init($, undefined) {
|
|
|
|
+ 'use strict';
|
|
|
|
+
|
|
|
|
+ // Polyfills Object.keys, if necessary.
|
|
|
|
+ // @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
|
|
|
|
+ if (!Object.keys) {
|
|
|
|
+ Object.keys = (function () {
|
|
|
|
+ var hasOwnProperty = Object.prototype.hasOwnProperty,
|
|
|
|
+ hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
|
|
|
|
+ dontEnums = [
|
|
|
|
+ 'toString',
|
|
|
|
+ 'toLocaleString',
|
|
|
|
+ 'valueOf',
|
|
|
|
+ 'hasOwnProperty',
|
|
|
|
+ 'isPrototypeOf',
|
|
|
|
+ 'propertyIsEnumerable',
|
|
|
|
+ 'constructor'
|
|
|
|
+ ],
|
|
|
|
+ dontEnumsLength = dontEnums.length;
|
|
|
|
+
|
|
|
|
+ return function (obj) {
|
|
|
|
+ if (typeof obj !== 'function' && (typeof obj !== 'object' || obj === null)) {
|
|
|
|
+ throw new TypeError('Object.keys called on non-object');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var result = [], prop, i;
|
|
|
|
+
|
|
|
|
+ for (prop in obj) {
|
|
|
|
+ if (hasOwnProperty.call(obj, prop)) {
|
|
|
|
+ result.push(prop);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (hasDontEnumBug) {
|
|
|
|
+ for (i = 0; i < dontEnumsLength; i++) {
|
|
|
|
+ if (hasOwnProperty.call(obj, dontEnums[i])) {
|
|
|
|
+ result.push(dontEnums[i]);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return result;
|
|
|
|
+ };
|
|
|
|
+ }());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var exports = {};
|
|
|
|
+
|
|
|
|
+ var VERSION = '5.0.0';
|
|
|
|
+ exports.VERSION = VERSION;
|
|
|
|
+
|
|
|
|
+ var locales = {};
|
|
|
|
+
|
|
|
|
+ var templates = {
|
|
|
|
+ dialog:
|
|
|
|
+ '<div class="bootbox modal" tabindex="-1" role="dialog" aria-hidden="true">' +
|
|
|
|
+ '<div class="modal-dialog">' +
|
|
|
|
+ '<div class="modal-content">' +
|
|
|
|
+ '<div class="modal-body"><div class="bootbox-body"></div></div>' +
|
|
|
|
+ '</div>' +
|
|
|
|
+ '</div>' +
|
|
|
|
+ '</div>',
|
|
|
|
+ header:
|
|
|
|
+ '<div class="modal-header">' +
|
|
|
|
+ '<h5 class="modal-title"></h5>' +
|
|
|
|
+ '</div>',
|
|
|
|
+ footer:
|
|
|
|
+ '<div class="modal-footer"></div>',
|
|
|
|
+ closeButton:
|
|
|
|
+ '<button type="button" class="bootbox-close-button close" aria-hidden="true">×</button>',
|
|
|
|
+ form:
|
|
|
|
+ '<form class="bootbox-form"></form>',
|
|
|
|
+ button:
|
|
|
|
+ '<button type="button" class="btn"></button>',
|
|
|
|
+ option:
|
|
|
|
+ '<option></option>',
|
|
|
|
+ promptMessage:
|
|
|
|
+ '<div class="bootbox-prompt-message"></div>',
|
|
|
|
+ inputs: {
|
|
|
|
+ text:
|
|
|
|
+ '<input class="bootbox-input bootbox-input-text form-control" autocomplete="off" type="text" />',
|
|
|
|
+ textarea:
|
|
|
|
+ '<textarea class="bootbox-input bootbox-input-textarea form-control"></textarea>',
|
|
|
|
+ email:
|
|
|
|
+ '<input class="bootbox-input bootbox-input-email form-control" autocomplete="off" type="email" />',
|
|
|
|
+ select:
|
|
|
|
+ '<select class="bootbox-input bootbox-input-select form-control"></select>',
|
|
|
|
+ checkbox:
|
|
|
|
+ '<div class="form-check checkbox"><label class="form-check-label"><input class="form-check-input bootbox-input bootbox-input-checkbox" type="checkbox" /></label></div>',
|
|
|
|
+ radio:
|
|
|
|
+ '<div class="form-check radio"><label class="form-check-label"><input class="form-check-input bootbox-input bootbox-input-radio" type="radio" name="bootbox-radio" /></label></div>',
|
|
|
|
+ date:
|
|
|
|
+ '<input class="bootbox-input bootbox-input-date form-control" autocomplete="off" type="date" />',
|
|
|
|
+ time:
|
|
|
|
+ '<input class="bootbox-input bootbox-input-time form-control" autocomplete="off" type="time" />',
|
|
|
|
+ number:
|
|
|
|
+ '<input class="bootbox-input bootbox-input-number form-control" autocomplete="off" type="number" />',
|
|
|
|
+ password:
|
|
|
|
+ '<input class="bootbox-input bootbox-input-password form-control" autocomplete="off" type="password" />',
|
|
|
|
+ range:
|
|
|
|
+ '<input class="bootbox-input bootbox-input-range form-control-range" autocomplete="off" type="range" />'
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ var defaults = {
|
|
|
|
+ // default language
|
|
|
|
+ locale: 'en',
|
|
|
|
+ // show backdrop or not. Default to static so user has to interact with dialog
|
|
|
|
+ backdrop: 'static',
|
|
|
|
+ // animate the modal in/out
|
|
|
|
+ animate: true,
|
|
|
|
+ // additional class string applied to the top level dialog
|
|
|
|
+ className: null,
|
|
|
|
+ // whether or not to include a close button
|
|
|
|
+ closeButton: true,
|
|
|
|
+ // show the dialog immediately by default
|
|
|
|
+ show: true,
|
|
|
|
+ // dialog container
|
|
|
|
+ container: 'body',
|
|
|
|
+ // default value (used by the prompt helper)
|
|
|
|
+ value: '',
|
|
|
|
+ // default input type (used by the prompt helper)
|
|
|
|
+ inputType: 'text',
|
|
|
|
+ // switch button order from cancel/confirm (default) to confirm/cancel
|
|
|
|
+ swapButtonOrder: false,
|
|
|
|
+ // center modal vertically in page
|
|
|
|
+ centerVertical: false,
|
|
|
|
+ // Append "multiple" property to the select when using the "prompt" helper
|
|
|
|
+ multiple: false,
|
|
|
|
+ // Automatically scroll modal content when height exceeds viewport height
|
|
|
|
+ scrollable: false
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // PUBLIC FUNCTIONS
|
|
|
|
+ // *************************************************************************************************************
|
|
|
|
+
|
|
|
|
+ // Return all currently registered locales, or a specific locale if "name" is defined
|
|
|
|
+ exports.locales = function (name) {
|
|
|
|
+ return name ? locales[name] : locales;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Register localized strings for the OK, Confirm, and Cancel buttons
|
|
|
|
+ exports.addLocale = function (name, values) {
|
|
|
|
+ $.each(['OK', 'CANCEL', 'CONFIRM'], function (_, v) {
|
|
|
|
+ if (!values[v]) {
|
|
|
|
+ throw new Error('Please supply a translation for "' + v + '"');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ locales[name] = {
|
|
|
|
+ OK: values.OK,
|
|
|
|
+ CANCEL: values.CANCEL,
|
|
|
|
+ CONFIRM: values.CONFIRM
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return exports;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Remove a previously-registered locale
|
|
|
|
+ exports.removeLocale = function (name) {
|
|
|
|
+ if (name !== 'en') {
|
|
|
|
+ delete locales[name];
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ throw new Error('"en" is used as the default and fallback locale and cannot be removed.');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return exports;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Set the default locale
|
|
|
|
+ exports.setLocale = function (name) {
|
|
|
|
+ return exports.setDefaults('locale', name);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Override default value(s) of Bootbox.
|
|
|
|
+ exports.setDefaults = function () {
|
|
|
|
+ var values = {};
|
|
|
|
+
|
|
|
|
+ if (arguments.length === 2) {
|
|
|
|
+ // allow passing of single key/value...
|
|
|
|
+ values[arguments[0]] = arguments[1];
|
|
|
|
+ } else {
|
|
|
|
+ // ... and as an object too
|
|
|
|
+ values = arguments[0];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $.extend(defaults, values);
|
|
|
|
+
|
|
|
|
+ return exports;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Hides all currently active Bootbox modals
|
|
|
|
+ exports.hideAll = function () {
|
|
|
|
+ $('.bootbox').modal('hide');
|
|
|
|
+
|
|
|
|
+ return exports;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Allows the base init() function to be overridden
|
|
|
|
+ exports.init = function (_$) {
|
|
|
|
+ return init(_$ || $);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // CORE HELPER FUNCTIONS
|
|
|
|
+ // *************************************************************************************************************
|
|
|
|
+
|
|
|
|
+ // Core dialog function
|
|
|
|
+ exports.dialog = function (options) {
|
|
|
|
+ if ($.fn.modal === undefined) {
|
|
|
|
+ throw new Error(
|
|
|
|
+ '"$.fn.modal" is not defined; please double check you have included ' +
|
|
|
|
+ 'the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ ' +
|
|
|
|
+ 'for more details.'
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ options = sanitize(options);
|
|
|
|
+
|
|
|
|
+ if ($.fn.modal.Constructor.VERSION) {
|
|
|
|
+ options.fullBootstrapVersion = $.fn.modal.Constructor.VERSION;
|
|
|
|
+ var i = options.fullBootstrapVersion.indexOf('.');
|
|
|
|
+ options.bootstrap = options.fullBootstrapVersion.substring(0, i);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ // Assuming version 2.3.2, as that was the last "supported" 2.x version
|
|
|
|
+ options.bootstrap = '2';
|
|
|
|
+ options.fullBootstrapVersion = '2.3.2';
|
|
|
|
+ console.warn('Bootbox will *mostly* work with Bootstrap 2, but we do not officially support it. Please upgrade, if possible.');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var dialog = $(templates.dialog);
|
|
|
|
+ var innerDialog = dialog.find('.modal-dialog');
|
|
|
|
+ var body = dialog.find('.modal-body');
|
|
|
|
+ var header = $(templates.header);
|
|
|
|
+ var footer = $(templates.footer);
|
|
|
|
+ var buttons = options.buttons;
|
|
|
|
+
|
|
|
|
+ var callbacks = {
|
|
|
|
+ onEscape: options.onEscape
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ body.find('.bootbox-body').html(options.message);
|
|
|
|
+
|
|
|
|
+ // Only attempt to create buttons if at least one has
|
|
|
|
+ // been defined in the options object
|
|
|
|
+ if (getKeyLength(options.buttons) > 0) {
|
|
|
|
+ each(buttons, function (key, b) {
|
|
|
|
+ var button = $(templates.button);
|
|
|
|
+ button.data('bb-handler', key);
|
|
|
|
+ button.addClass(b.className);
|
|
|
|
+
|
|
|
|
+ switch(key)
|
|
|
|
+ {
|
|
|
|
+ case 'ok':
|
|
|
|
+ case 'confirm':
|
|
|
|
+ button.addClass('bootbox-accept');
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 'cancel':
|
|
|
|
+ button.addClass('bootbox-cancel');
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ button.html(b.label);
|
|
|
|
+ footer.append(button);
|
|
|
|
+
|
|
|
|
+ callbacks[key] = b.callback;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ body.after(footer);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.animate === true) {
|
|
|
|
+ dialog.addClass('fade');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.className) {
|
|
|
|
+ dialog.addClass(options.className);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.size) {
|
|
|
|
+ // Requires Bootstrap 3.1.0 or higher
|
|
|
|
+ if (options.fullBootstrapVersion.substring(0, 3) < '3.1') {
|
|
|
|
+ console.warn('"size" requires Bootstrap 3.1.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switch(options.size)
|
|
|
|
+ {
|
|
|
|
+ case 'small':
|
|
|
|
+ case 'sm':
|
|
|
|
+ innerDialog.addClass('modal-sm');
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 'large':
|
|
|
|
+ case 'lg':
|
|
|
|
+ innerDialog.addClass('modal-lg');
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 'xl':
|
|
|
|
+ case 'extra-large':
|
|
|
|
+ // Requires Bootstrap 4.2.0 or higher
|
|
|
|
+ if (options.fullBootstrapVersion.substring(0, 3) < '4.2') {
|
|
|
|
+ console.warn('Using size "xl"/"extra-large" requires Bootstrap 4.2.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
|
|
|
|
+ }
|
|
|
|
+ innerDialog.addClass('modal-xl');
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(options.scrollable){
|
|
|
|
+ // Requires Bootstrap 4.3.0 or higher
|
|
|
|
+ if (options.fullBootstrapVersion.substring(0, 3) < '4.3') {
|
|
|
|
+ console.warn('Using "scrollable" requires Bootstrap 4.3.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ innerDialog.addClass('modal-dialog-scrollable');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.title) {
|
|
|
|
+ body.before(header);
|
|
|
|
+ dialog.find('.modal-title').html(options.title);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.closeButton) {
|
|
|
|
+ var closeButton = $(templates.closeButton);
|
|
|
|
+
|
|
|
|
+ if (options.title) {
|
|
|
|
+ if (options.bootstrap > 3) {
|
|
|
|
+ dialog.find('.modal-header').append(closeButton);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ dialog.find('.modal-header').prepend(closeButton);
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ closeButton.prependTo(body);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(options.centerVertical){
|
|
|
|
+ // Requires Bootstrap 4.0.0-beta.3 or higher
|
|
|
|
+ if (options.fullBootstrapVersion < '4.0.0') {
|
|
|
|
+ console.warn('"centerVertical" requires Bootstrap 4.0.0-beta.3 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ innerDialog.addClass('modal-dialog-centered');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Bootstrap event listeners; these handle extra
|
|
|
|
+ // setup & teardown required after the underlying
|
|
|
|
+ // modal has performed certain actions.
|
|
|
|
+
|
|
|
|
+ // make sure we unbind any listeners once the dialog has definitively been dismissed
|
|
|
|
+ dialog.one('hide.bs.modal', function (e) {
|
|
|
|
+ if (e.target === this) {
|
|
|
|
+ dialog.off('escape.close.bb');
|
|
|
|
+ dialog.off('click');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ dialog.one('hidden.bs.modal', function (e) {
|
|
|
|
+ // ensure we don't accidentally intercept hidden events triggered
|
|
|
|
+ // by children of the current dialog. We shouldn't need to handle this anymore,
|
|
|
|
+ // now that Bootstrap namespaces its events, but still worth doing.
|
|
|
|
+ if (e.target === this) {
|
|
|
|
+ dialog.remove();
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ dialog.one('shown.bs.modal', function () {
|
|
|
|
+ dialog.find('.bootbox-accept').first().trigger('focus');
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // Bootbox event listeners; used to decouple some
|
|
|
|
+ // behaviours from their respective triggers
|
|
|
|
+
|
|
|
|
+ if (options.backdrop !== 'static') {
|
|
|
|
+ // A boolean true/false according to the Bootstrap docs
|
|
|
|
+ // should show a dialog the user can dismiss by clicking on
|
|
|
|
+ // the background.
|
|
|
|
+ // We always only ever pass static/false to the actual
|
|
|
|
+ // $.modal function because with "true" we can't trap
|
|
|
|
+ // this event (the .modal-backdrop swallows it)
|
|
|
|
+ // However, we still want to sort of respect true
|
|
|
|
+ // and invoke the escape mechanism instead
|
|
|
|
+ dialog.on('click.dismiss.bs.modal', function (e) {
|
|
|
|
+ // @NOTE: the target varies in >= 3.3.x releases since the modal backdrop
|
|
|
|
+ // moved *inside* the outer dialog rather than *alongside* it
|
|
|
|
+ if (dialog.children('.modal-backdrop').length) {
|
|
|
|
+ e.currentTarget = dialog.children('.modal-backdrop').get(0);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (e.target !== e.currentTarget) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dialog.trigger('escape.close.bb');
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dialog.on('escape.close.bb', function (e) {
|
|
|
|
+ // the if statement looks redundant but it isn't; without it
|
|
|
|
+ // if we *didn't* have an onEscape handler then processCallback
|
|
|
|
+ // would automatically dismiss the dialog
|
|
|
|
+ if (callbacks.onEscape) {
|
|
|
|
+ processCallback(e, dialog, callbacks.onEscape);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ dialog.on('click', '.modal-footer button:not(.disabled)', function (e) {
|
|
|
|
+ var callbackKey = $(this).data('bb-handler');
|
|
|
|
+
|
|
|
|
+ if (callbackKey !== undefined) {
|
|
|
|
+ // Only process callbacks for buttons we recognize:
|
|
|
|
+ processCallback(e, dialog, callbacks[callbackKey]);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ dialog.on('click', '.bootbox-close-button', function (e) {
|
|
|
|
+ // onEscape might be falsy but that's fine; the fact is
|
|
|
|
+ // if the user has managed to click the close button we
|
|
|
|
+ // have to close the dialog, callback or not
|
|
|
|
+ processCallback(e, dialog, callbacks.onEscape);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ dialog.on('keyup', function (e) {
|
|
|
|
+ if (e.which === 27) {
|
|
|
|
+ dialog.trigger('escape.close.bb');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // the remainder of this method simply deals with adding our
|
|
|
|
+ // dialogent to the DOM, augmenting it with Bootstrap's modal
|
|
|
|
+ // functionality and then giving the resulting object back
|
|
|
|
+ // to our caller
|
|
|
|
+
|
|
|
|
+ $(options.container).append(dialog);
|
|
|
|
+
|
|
|
|
+ dialog.modal({
|
|
|
|
+ backdrop: options.backdrop ? 'static' : false,
|
|
|
|
+ keyboard: false,
|
|
|
|
+ show: false
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if (options.show) {
|
|
|
|
+ dialog.modal('show');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return dialog;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Helper function to simulate the native alert() behavior. **NOTE**: This is non-blocking, so any
|
|
|
|
+ // code that must happen after the alert is dismissed should be placed within the callback function
|
|
|
|
+ // for this alert.
|
|
|
|
+ exports.alert = function () {
|
|
|
|
+ var options;
|
|
|
|
+
|
|
|
|
+ options = mergeDialogOptions('alert', ['ok'], ['message', 'callback'], arguments);
|
|
|
|
+
|
|
|
|
+ // @TODO: can this move inside exports.dialog when we're iterating over each
|
|
|
|
+ // button and checking its button.callback value instead?
|
|
|
|
+ if (options.callback && !$.isFunction(options.callback)) {
|
|
|
|
+ throw new Error('alert requires the "callback" property to be a function when provided');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // override the ok and escape callback to make sure they just invoke
|
|
|
|
+ // the single user-supplied one (if provided)
|
|
|
|
+ options.buttons.ok.callback = options.onEscape = function () {
|
|
|
|
+ if ($.isFunction(options.callback)) {
|
|
|
|
+ return options.callback.call(this);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return exports.dialog(options);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Helper function to simulate the native confirm() behavior. **NOTE**: This is non-blocking, so any
|
|
|
|
+ // code that must happen after the confirm is dismissed should be placed within the callback function
|
|
|
|
+ // for this confirm.
|
|
|
|
+ exports.confirm = function () {
|
|
|
|
+ var options;
|
|
|
|
+
|
|
|
|
+ options = mergeDialogOptions('confirm', ['cancel', 'confirm'], ['message', 'callback'], arguments);
|
|
|
|
+
|
|
|
|
+ // confirm specific validation; they don't make sense without a callback so make
|
|
|
|
+ // sure it's present
|
|
|
|
+ if (!$.isFunction(options.callback)) {
|
|
|
|
+ throw new Error('confirm requires a callback');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // overrides; undo anything the user tried to set they shouldn't have
|
|
|
|
+ options.buttons.cancel.callback = options.onEscape = function () {
|
|
|
|
+ return options.callback.call(this, false);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ options.buttons.confirm.callback = function () {
|
|
|
|
+ return options.callback.call(this, true);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return exports.dialog(options);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Helper function to simulate the native prompt() behavior. **NOTE**: This is non-blocking, so any
|
|
|
|
+ // code that must happen after the prompt is dismissed should be placed within the callback function
|
|
|
|
+ // for this prompt.
|
|
|
|
+ exports.prompt = function () {
|
|
|
|
+ var options;
|
|
|
|
+ var promptDialog;
|
|
|
|
+ var form;
|
|
|
|
+ var input;
|
|
|
|
+ var shouldShow;
|
|
|
|
+ var inputOptions;
|
|
|
|
+
|
|
|
|
+ // we have to create our form first otherwise
|
|
|
|
+ // its value is undefined when gearing up our options
|
|
|
|
+ // @TODO this could be solved by allowing message to
|
|
|
|
+ // be a function instead...
|
|
|
|
+ form = $(templates.form);
|
|
|
|
+
|
|
|
|
+ // prompt defaults are more complex than others in that
|
|
|
|
+ // users can override more defaults
|
|
|
|
+ options = mergeDialogOptions('prompt', ['cancel', 'confirm'], ['title', 'callback'], arguments);
|
|
|
|
+
|
|
|
|
+ if (!options.value) {
|
|
|
|
+ options.value = defaults.value;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!options.inputType) {
|
|
|
|
+ options.inputType = defaults.inputType;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // capture the user's show value; we always set this to false before
|
|
|
|
+ // spawning the dialog to give us a chance to attach some handlers to
|
|
|
|
+ // it, but we need to make sure we respect a preference not to show it
|
|
|
|
+ shouldShow = (options.show === undefined) ? defaults.show : options.show;
|
|
|
|
+ // This is required prior to calling the dialog builder below - we need to
|
|
|
|
+ // add an event handler just before the prompt is shown
|
|
|
|
+ options.show = false;
|
|
|
|
+
|
|
|
|
+ // Handles the 'cancel' action
|
|
|
|
+ options.buttons.cancel.callback = options.onEscape = function () {
|
|
|
|
+ return options.callback.call(this, null);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // Prompt submitted - extract the prompt value. This requires a bit of work,
|
|
|
|
+ // given the different input types available.
|
|
|
|
+ options.buttons.confirm.callback = function () {
|
|
|
|
+ var value;
|
|
|
|
+
|
|
|
|
+ if (options.inputType === 'checkbox') {
|
|
|
|
+ value = input.find('input:checked').map(function () {
|
|
|
|
+ return $(this).val();
|
|
|
|
+ }).get();
|
|
|
|
+ } else if (options.inputType === 'radio') {
|
|
|
|
+ value = input.find('input:checked').val();
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ if (input[0].checkValidity && !input[0].checkValidity()) {
|
|
|
|
+ // prevents button callback from being called
|
|
|
|
+ return false;
|
|
|
|
+ } else {
|
|
|
|
+ if (options.inputType === 'select' && options.multiple === true) {
|
|
|
|
+ value = input.find('option:selected').map(function () {
|
|
|
|
+ return $(this).val();
|
|
|
|
+ }).get();
|
|
|
|
+ }
|
|
|
|
+ else{
|
|
|
|
+ value = input.val();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return options.callback.call(this, value);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // prompt-specific validation
|
|
|
|
+ if (!options.title) {
|
|
|
|
+ throw new Error('prompt requires a title');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!$.isFunction(options.callback)) {
|
|
|
|
+ throw new Error('prompt requires a callback');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!templates.inputs[options.inputType]) {
|
|
|
|
+ throw new Error('Invalid prompt type');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // create the input based on the supplied type
|
|
|
|
+ input = $(templates.inputs[options.inputType]);
|
|
|
|
+
|
|
|
|
+ switch (options.inputType) {
|
|
|
|
+ case 'text':
|
|
|
|
+ case 'textarea':
|
|
|
|
+ case 'email':
|
|
|
|
+ case 'password':
|
|
|
|
+ input.val(options.value);
|
|
|
|
+
|
|
|
|
+ if (options.placeholder) {
|
|
|
|
+ input.attr('placeholder', options.placeholder);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.pattern) {
|
|
|
|
+ input.attr('pattern', options.pattern);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.maxlength) {
|
|
|
|
+ input.attr('maxlength', options.maxlength);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.required) {
|
|
|
|
+ input.prop({ 'required': true });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.rows && !isNaN(parseInt(options.rows))) {
|
|
|
|
+ if(options.inputType === 'textarea'){
|
|
|
|
+ input.attr({ 'rows': options.rows });
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ case 'date':
|
|
|
|
+ case 'time':
|
|
|
|
+ case 'number':
|
|
|
|
+ case 'range':
|
|
|
|
+ input.val(options.value);
|
|
|
|
+
|
|
|
|
+ if (options.placeholder) {
|
|
|
|
+ input.attr('placeholder', options.placeholder);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.pattern) {
|
|
|
|
+ input.attr('pattern', options.pattern);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.required) {
|
|
|
|
+ input.prop({ 'required': true });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // These input types have extra attributes which affect their input validation.
|
|
|
|
+ // Warning: For most browsers, date inputs are buggy in their implementation of 'step', so
|
|
|
|
+ // this attribute will have no effect. Therefore, we don't set the attribute for date inputs.
|
|
|
|
+ // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date#Setting_maximum_and_minimum_dates
|
|
|
|
+ if (options.inputType !== 'date') {
|
|
|
|
+ if (options.step) {
|
|
|
|
+ if (options.step === 'any' || (!isNaN(options.step) && parseFloat(options.step) > 0)) {
|
|
|
|
+ input.attr('step', options.step);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ throw new Error('"step" must be a valid positive number or the value "any". See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-step for more information.');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(minAndMaxAreValid(options.inputType, options.min, options.max)){
|
|
|
|
+ if(options.min !== undefined){
|
|
|
|
+ input.attr('min', options.min);
|
|
|
|
+ }
|
|
|
|
+ if(options.max !== undefined){
|
|
|
|
+ input.attr('max', options.max);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ case 'select':
|
|
|
|
+ var groups = {};
|
|
|
|
+ inputOptions = options.inputOptions || [];
|
|
|
|
+
|
|
|
|
+ if (!$.isArray(inputOptions)) {
|
|
|
|
+ throw new Error('Please pass an array of input options');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!inputOptions.length) {
|
|
|
|
+ throw new Error('prompt with "inputType" set to "select" requires at least one option');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // placeholder is not actually a valid attribute for select,
|
|
|
|
+ // but we'll allow it, assuming it might be used for a plugin
|
|
|
|
+ if (options.placeholder) {
|
|
|
|
+ input.attr('placeholder', options.placeholder);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.required) {
|
|
|
|
+ input.prop({ 'required': true });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (options.multiple) {
|
|
|
|
+ input.prop({ 'multiple': true });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ each(inputOptions, function (_, option) {
|
|
|
|
+ // assume the element to attach to is the input...
|
|
|
|
+ var elem = input;
|
|
|
|
+
|
|
|
|
+ if (option.value === undefined || option.text === undefined) {
|
|
|
|
+ throw new Error('each option needs a "value" property and a "text" property');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // ... but override that element if this option sits in a group
|
|
|
|
+
|
|
|
|
+ if (option.group) {
|
|
|
|
+ // initialise group if necessary
|
|
|
|
+ if (!groups[option.group]) {
|
|
|
|
+ groups[option.group] = $('<optgroup />').attr('label', option.group);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ elem = groups[option.group];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var o = $(templates.option);
|
|
|
|
+ o.attr('value', option.value).text(option.text);
|
|
|
|
+ elem.append(o);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ each(groups, function (_, group) {
|
|
|
|
+ input.append(group);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // safe to set a select's value as per a normal input
|
|
|
|
+ input.val(options.value);
|
|
|
|
+
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ case 'checkbox':
|
|
|
|
+ var checkboxValues = $.isArray(options.value) ? options.value : [options.value];
|
|
|
|
+ inputOptions = options.inputOptions || [];
|
|
|
|
+
|
|
|
|
+ if (!inputOptions.length) {
|
|
|
|
+ throw new Error('prompt with "inputType" set to "checkbox" requires at least one option');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // checkboxes have to nest within a containing element, so
|
|
|
|
+ // they break the rules a bit and we end up re-assigning
|
|
|
|
+ // our 'input' element to this container instead
|
|
|
|
+ input = $('<div class="bootbox-checkbox-list"></div>');
|
|
|
|
+
|
|
|
|
+ each(inputOptions, function (_, option) {
|
|
|
|
+ if (option.value === undefined || option.text === undefined) {
|
|
|
|
+ throw new Error('each option needs a "value" property and a "text" property');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var checkbox = $(templates.inputs[options.inputType]);
|
|
|
|
+
|
|
|
|
+ checkbox.find('input').attr('value', option.value);
|
|
|
|
+ checkbox.find('label').append('\n' + option.text);
|
|
|
|
+
|
|
|
|
+ // we've ensured values is an array so we can always iterate over it
|
|
|
|
+ each(checkboxValues, function (_, value) {
|
|
|
|
+ if (value === option.value) {
|
|
|
|
+ checkbox.find('input').prop('checked', true);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ input.append(checkbox);
|
|
|
|
+ });
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ case 'radio':
|
|
|
|
+ // Make sure that value is not an array (only a single radio can ever be checked)
|
|
|
|
+ if (options.value !== undefined && $.isArray(options.value)) {
|
|
|
|
+ throw new Error('prompt with "inputType" set to "radio" requires a single, non-array value for "value"');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ inputOptions = options.inputOptions || [];
|
|
|
|
+
|
|
|
|
+ if (!inputOptions.length) {
|
|
|
|
+ throw new Error('prompt with "inputType" set to "radio" requires at least one option');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Radiobuttons have to nest within a containing element, so
|
|
|
|
+ // they break the rules a bit and we end up re-assigning
|
|
|
|
+ // our 'input' element to this container instead
|
|
|
|
+ input = $('<div class="bootbox-radiobutton-list"></div>');
|
|
|
|
+
|
|
|
|
+ // Radiobuttons should always have an initial checked input checked in a "group".
|
|
|
|
+ // If value is undefined or doesn't match an input option, select the first radiobutton
|
|
|
|
+ var checkFirstRadio = true;
|
|
|
|
+
|
|
|
|
+ each(inputOptions, function (_, option) {
|
|
|
|
+ if (option.value === undefined || option.text === undefined) {
|
|
|
|
+ throw new Error('each option needs a "value" property and a "text" property');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var radio = $(templates.inputs[options.inputType]);
|
|
|
|
+
|
|
|
|
+ radio.find('input').attr('value', option.value);
|
|
|
|
+ radio.find('label').append('\n' + option.text);
|
|
|
|
+
|
|
|
|
+ if (options.value !== undefined) {
|
|
|
|
+ if (option.value === options.value) {
|
|
|
|
+ radio.find('input').prop('checked', true);
|
|
|
|
+ checkFirstRadio = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ input.append(radio);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if (checkFirstRadio) {
|
|
|
|
+ input.find('input[type="radio"]').first().prop('checked', true);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // now place it in our form
|
|
|
|
+ form.append(input);
|
|
|
|
+
|
|
|
|
+ form.on('submit', function (e) {
|
|
|
|
+ e.preventDefault();
|
|
|
|
+ // Fix for SammyJS (or similar JS routing library) hijacking the form post.
|
|
|
|
+ e.stopPropagation();
|
|
|
|
+
|
|
|
|
+ // @TODO can we actually click *the* button object instead?
|
|
|
|
+ // e.g. buttons.confirm.click() or similar
|
|
|
|
+ promptDialog.find('.bootbox-accept').trigger('click');
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if ($.trim(options.message) !== '') {
|
|
|
|
+ // Add the form to whatever content the user may have added.
|
|
|
|
+ var message = $(templates.promptMessage).html(options.message);
|
|
|
|
+ form.prepend(message);
|
|
|
|
+ options.message = form;
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ options.message = form;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Generate the dialog
|
|
|
|
+ promptDialog = exports.dialog(options);
|
|
|
|
+
|
|
|
|
+ // clear the existing handler focusing the submit button...
|
|
|
|
+ promptDialog.off('shown.bs.modal');
|
|
|
|
+
|
|
|
|
+ // ...and replace it with one focusing our input, if possible
|
|
|
|
+ promptDialog.on('shown.bs.modal', function () {
|
|
|
|
+ // need the closure here since input isn't
|
|
|
|
+ // an object otherwise
|
|
|
|
+ input.focus();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if (shouldShow === true) {
|
|
|
|
+ promptDialog.modal('show');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return promptDialog;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // INTERNAL FUNCTIONS
|
|
|
|
+ // *************************************************************************************************************
|
|
|
|
+
|
|
|
|
+ // Map a flexible set of arguments into a single returned object
|
|
|
|
+ // If args.length is already one just return it, otherwise
|
|
|
|
+ // use the properties argument to map the unnamed args to
|
|
|
|
+ // object properties.
|
|
|
|
+ // So in the latter case:
|
|
|
|
+ // mapArguments(["foo", $.noop], ["message", "callback"])
|
|
|
|
+ // -> { message: "foo", callback: $.noop }
|
|
|
|
+ function mapArguments(args, properties) {
|
|
|
|
+ var argn = args.length;
|
|
|
|
+ var options = {};
|
|
|
|
+
|
|
|
|
+ if (argn < 1 || argn > 2) {
|
|
|
|
+ throw new Error('Invalid argument length');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (argn === 2 || typeof args[0] === 'string') {
|
|
|
|
+ options[properties[0]] = args[0];
|
|
|
|
+ options[properties[1]] = args[1];
|
|
|
|
+ } else {
|
|
|
|
+ options = args[0];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return options;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Merge a set of default dialog options with user supplied arguments
|
|
|
|
+ function mergeArguments(defaults, args, properties) {
|
|
|
|
+ return $.extend(
|
|
|
|
+ // deep merge
|
|
|
|
+ true,
|
|
|
|
+ // ensure the target is an empty, unreferenced object
|
|
|
|
+ {},
|
|
|
|
+ // the base options object for this type of dialog (often just buttons)
|
|
|
|
+ defaults,
|
|
|
|
+ // args could be an object or array; if it's an array properties will
|
|
|
|
+ // map it to a proper options object
|
|
|
|
+ mapArguments(
|
|
|
|
+ args,
|
|
|
|
+ properties
|
|
|
|
+ )
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // This entry-level method makes heavy use of composition to take a simple
|
|
|
|
+ // range of inputs and return valid options suitable for passing to bootbox.dialog
|
|
|
|
+ function mergeDialogOptions(className, labels, properties, args) {
|
|
|
|
+ var locale;
|
|
|
|
+ if(args && args[0]){
|
|
|
|
+ locale = args[0].locale || defaults.locale;
|
|
|
|
+ var swapButtons = args[0].swapButtonOrder || defaults.swapButtonOrder;
|
|
|
|
+
|
|
|
|
+ if(swapButtons){
|
|
|
|
+ labels = labels.reverse();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // build up a base set of dialog properties
|
|
|
|
+ var baseOptions = {
|
|
|
|
+ className: 'bootbox-' + className,
|
|
|
|
+ buttons: createLabels(labels, locale)
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // Ensure the buttons properties generated, *after* merging
|
|
|
|
+ // with user args are still valid against the supplied labels
|
|
|
|
+ return validateButtons(
|
|
|
|
+ // merge the generated base properties with user supplied arguments
|
|
|
|
+ mergeArguments(
|
|
|
|
+ baseOptions,
|
|
|
|
+ args,
|
|
|
|
+ // if args.length > 1, properties specify how each arg maps to an object key
|
|
|
|
+ properties
|
|
|
|
+ ),
|
|
|
|
+ labels
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Checks each button object to see if key is valid.
|
|
|
|
+ // This function will only be called by the alert, confirm, and prompt helpers.
|
|
|
|
+ function validateButtons(options, buttons) {
|
|
|
|
+ var allowedButtons = {};
|
|
|
|
+ each(buttons, function (key, value) {
|
|
|
|
+ allowedButtons[value] = true;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ each(options.buttons, function (key) {
|
|
|
|
+ if (allowedButtons[key] === undefined) {
|
|
|
|
+ throw new Error('button key "' + key + '" is not allowed (options are ' + buttons.join(' ') + ')');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ return options;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // From a given list of arguments, return a suitable object of button labels.
|
|
|
|
+ // All this does is normalise the given labels and translate them where possible.
|
|
|
|
+ // e.g. "ok", "confirm" -> { ok: "OK", cancel: "Annuleren" }
|
|
|
|
+ function createLabels(labels, locale) {
|
|
|
|
+ var buttons = {};
|
|
|
|
+
|
|
|
|
+ for (var i = 0, j = labels.length; i < j; i++) {
|
|
|
|
+ var argument = labels[i];
|
|
|
|
+ var key = argument.toLowerCase();
|
|
|
|
+ var value = argument.toUpperCase();
|
|
|
|
+
|
|
|
|
+ buttons[key] = {
|
|
|
|
+ label: getText(value, locale)
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return buttons;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Get localized text from a locale. Defaults to 'en' locale if no locale
|
|
|
|
+ // provided or a non-registered locale is requested
|
|
|
|
+ function getText(key, locale) {
|
|
|
|
+ var labels = locales[locale];
|
|
|
|
+
|
|
|
|
+ return labels ? labels[key] : locales.en[key];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Filter and tidy up any user supplied parameters to this dialog.
|
|
|
|
+ // Also looks for any shorthands used and ensures that the options
|
|
|
|
+ // which are returned are all normalized properly
|
|
|
|
+ function sanitize(options) {
|
|
|
|
+ var buttons;
|
|
|
|
+ var total;
|
|
|
|
+
|
|
|
|
+ if (typeof options !== 'object') {
|
|
|
|
+ throw new Error('Please supply an object of options');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!options.message) {
|
|
|
|
+ throw new Error('"message" option must not be null or an empty string.');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // make sure any supplied options take precedence over defaults
|
|
|
|
+ options = $.extend({}, defaults, options);
|
|
|
|
+
|
|
|
|
+ // no buttons is still a valid dialog but it's cleaner to always have
|
|
|
|
+ // a buttons object to iterate over, even if it's empty
|
|
|
|
+ if (!options.buttons) {
|
|
|
|
+ options.buttons = {};
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ buttons = options.buttons;
|
|
|
|
+
|
|
|
|
+ total = getKeyLength(buttons);
|
|
|
|
+
|
|
|
|
+ each(buttons, function (key, button, index) {
|
|
|
|
+ if ($.isFunction(button)) {
|
|
|
|
+ // short form, assume value is our callback. Since button
|
|
|
|
+ // isn't an object it isn't a reference either so re-assign it
|
|
|
|
+ button = buttons[key] = {
|
|
|
|
+ callback: button
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // before any further checks make sure by now button is the correct type
|
|
|
|
+ if ($.type(button) !== 'object') {
|
|
|
|
+ throw new Error('button with key "' + key + '" must be an object');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!button.label) {
|
|
|
|
+ // the lack of an explicit label means we'll assume the key is good enough
|
|
|
|
+ button.label = key;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!button.className) {
|
|
|
|
+ var isPrimary = false;
|
|
|
|
+ if(options.swapButtonOrder){
|
|
|
|
+ isPrimary = index === 0;
|
|
|
|
+ }
|
|
|
|
+ else{
|
|
|
|
+ isPrimary = index === total-1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (total <= 2 && isPrimary) {
|
|
|
|
+ // always add a primary to the main option in a one or two-button dialog
|
|
|
|
+ button.className = 'btn-primary';
|
|
|
|
+ } else {
|
|
|
|
+ // adding both classes allows us to target both BS3 and BS4 without needing to check the version
|
|
|
|
+ button.className = 'btn-secondary btn-default';
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ return options;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Returns a count of the properties defined on the object
|
|
|
|
+ function getKeyLength(obj) {
|
|
|
|
+ return Object.keys(obj).length;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Tiny wrapper function around jQuery.each; just adds index as the third parameter
|
|
|
|
+ function each(collection, iterator) {
|
|
|
|
+ var index = 0;
|
|
|
|
+ $.each(collection, function (key, value) {
|
|
|
|
+ iterator(key, value, index++);
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Handle the invoked dialog callback
|
|
|
|
+ function processCallback(e, dialog, callback) {
|
|
|
|
+ e.stopPropagation();
|
|
|
|
+ e.preventDefault();
|
|
|
|
+
|
|
|
|
+ // by default we assume a callback will get rid of the dialog,
|
|
|
|
+ // although it is given the opportunity to override this
|
|
|
|
+
|
|
|
|
+ // so, if the callback can be invoked and it *explicitly returns false*
|
|
|
|
+ // then we'll set a flag to keep the dialog active...
|
|
|
|
+ var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false;
|
|
|
|
+
|
|
|
|
+ // ... otherwise we'll bin it
|
|
|
|
+ if (!preserveDialog) {
|
|
|
|
+ dialog.modal('hide');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Validate `min` and `max` values based on the current `inputType` value
|
|
|
|
+ function minAndMaxAreValid(type, min, max){
|
|
|
|
+ var result = false;
|
|
|
|
+ var minValid = true;
|
|
|
|
+ var maxValid = true;
|
|
|
|
+
|
|
|
|
+ if (type === 'date') {
|
|
|
|
+ if (min !== undefined && !(minValid = dateIsValid(min))) {
|
|
|
|
+ console.warn('Browsers which natively support the "date" input type expect date values to be of the form "YYYY-MM-DD" (see ISO-8601 https://www.iso.org/iso-8601-date-and-time-format.html). Bootbox does not enforce this rule, but your min value may not be enforced by this browser.');
|
|
|
|
+ }
|
|
|
|
+ else if (max !== undefined && !(maxValid = dateIsValid(max))) {
|
|
|
|
+ console.warn('Browsers which natively support the "date" input type expect date values to be of the form "YYYY-MM-DD" (see ISO-8601 https://www.iso.org/iso-8601-date-and-time-format.html). Bootbox does not enforce this rule, but your max value may not be enforced by this browser.');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (type === 'time') {
|
|
|
|
+ if (min !== undefined && !(minValid = timeIsValid(min))) {
|
|
|
|
+ throw new Error('"min" is not a valid time. See https://www.w3.org/TR/2012/WD-html-markup-20120315/datatypes.html#form.data.time for more information.');
|
|
|
|
+ }
|
|
|
|
+ else if (max !== undefined && !(maxValid = timeIsValid(max))) {
|
|
|
|
+ throw new Error('"max" is not a valid time. See https://www.w3.org/TR/2012/WD-html-markup-20120315/datatypes.html#form.data.time for more information.');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ if (min !== undefined && isNaN(min)) {
|
|
|
|
+ minValid = false;
|
|
|
|
+ throw new Error('"min" must be a valid number. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-min for more information.');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (max !== undefined && isNaN(max)) {
|
|
|
|
+ maxValid = false;
|
|
|
|
+ throw new Error('"max" must be a valid number. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-max for more information.');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(minValid && maxValid){
|
|
|
|
+ if(max <= min){
|
|
|
|
+ throw new Error('"max" must be greater than "min". See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-max for more information.');
|
|
|
|
+ }
|
|
|
|
+ else{
|
|
|
|
+ result = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function timeIsValid(value){
|
|
|
|
+ return /([01][0-9]|2[0-3]):[0-5][0-9]?:[0-5][0-9]/.test(value);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function dateIsValid(value){
|
|
|
|
+ return /(\d{4})-(\d{2})-(\d{2})/.test(value);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Register the default locale
|
|
|
|
+ exports.addLocale('en', {
|
|
|
|
+ OK: 'OK',
|
|
|
|
+ CANCEL: 'Cancel',
|
|
|
|
+ CONFIRM: 'OK'
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // The Bootbox object
|
|
|
|
+ return exports;
|
|
|
|
+}));
|