/*! @preserve * bootbox.js * version: 5.5.2 * author: Nick Payne * 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.5.2'; exports.VERSION = VERSION; var locales = { en : { OK : 'OK', CANCEL : 'Cancel', CONFIRM : 'OK' } }; var templates = { dialog: '', header: '', footer: '', closeButton: '', form: '
', button: '', option: '', promptMessage: '
', inputs: { text: '', textarea: '', email: '', select: '', checkbox: '
', radio: '
', date: '', time: '', number: '', password: '', 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, // whether or not to destroy the modal on hide reusable: 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 https://getbootstrap.com/docs/4.4/getting-started/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 'extra-large': case 'xl': innerDialog.addClass('modal-xl'); // 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.'); } break; } } if (options.scrollable) { innerDialog.addClass('modal-dialog-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.'); } } 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) { innerDialog.addClass('modal-dialog-centered'); // 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.'); } } // Bootstrap event listeners; these handle extra // setup & teardown required after the underlying // modal has performed certain actions. if(!options.reusable) { // make sure we unbind any listeners once the dialog has definitively been dismissed dialog.one('hide.bs.modal', { dialog: dialog }, unbindModal); } if (options.onHide) { if ($.isFunction(options.onHide)) { dialog.on('hide.bs.modal', options.onHide); } else { throw new Error('Argument supplied to "onHide" must be a function'); } } if(!options.reusable) { dialog.one('hidden.bs.modal', { dialog: dialog }, destroyModal); } if (options.onHidden) { if ($.isFunction(options.onHidden)) { dialog.on('hidden.bs.modal', options.onHidden); } else { throw new Error('Argument supplied to "onHidden" must be a function'); } } if (options.onShow) { if ($.isFunction(options.onShow)) { dialog.on('show.bs.modal', options.onShow); } else { throw new Error('Argument supplied to "onShow" must be a function'); } } dialog.one('shown.bs.modal', { dialog: dialog }, focusPrimaryButton); if (options.onShown) { if ($.isFunction(options.onShown)) { dialog.on('shown.bs.modal', options.onShown); } else { throw new Error('Argument supplied to "onShown" must be a function'); } } // Bootbox event listeners; used to decouple some // behaviours from their respective triggers if (options.backdrop === true) { // 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 // dialog element 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, 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] = $('').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 = $('
'); 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 = $('
'); // 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', focusPrimaryButton); // ...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); //make sure backdrop is either true, false, or 'static' if (!options.backdrop) { options.backdrop = (options.backdrop === false || options.backdrop === 0) ? false : 'static'; } else { options.backdrop = typeof options.backdrop === 'string' && options.backdrop.toLowerCase() === 'static' ? 'static' : true; } // 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++); }); } function focusPrimaryButton(e) { e.data.dialog.find('.bootbox-accept').first().trigger('focus'); } function destroyModal(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 === e.data.dialog[0]) { e.data.dialog.remove(); } } function unbindModal(e) { if (e.target === e.data.dialog[0]) { e.data.dialog.off('escape.close.bb'); e.data.dialog.off('click'); } } // 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); } // The Bootbox object return exports; }));