vendor/assets/javascripts/bootstrapValidator.js in bootstrap-validator-rails-0.5.2 vs vendor/assets/javascripts/bootstrapValidator.js in bootstrap-validator-rails-0.5.3

- old
+ new

@@ -1,19 +1,27 @@ /*! * BootstrapValidator (http://bootstrapvalidator.com) * The best jQuery plugin to validate form fields. Designed to use with Bootstrap 3 * - * @version v0.5.2, built on 2014-09-25 4:01:07 PM + * @version v0.5.3, built on 2014-11-05 9:14:18 PM * @author https://twitter.com/nghuuphuoc * @copyright (c) 2013 - 2014 Nguyen Huu Phuoc - * @license MIT + * @license Commercial: http://bootstrapvalidator.com/license/ + * Non-commercial: http://creativecommons.org/licenses/by-nc-nd/3.0/ */ if (typeof jQuery === 'undefined') { - throw new Error('BootstrapValidator\'s JavaScript requires jQuery'); + throw new Error('BootstrapValidator requires jQuery'); } (function($) { + var version = $.fn.jquery.split(' ')[0].split('.'); + if ((+version[0] < 2 && +version[1] < 9) || (+version[0] === 1 && +version[1] === 9 && +version[2] < 1)) { + throw new Error('BootstrapValidator requires jQuery version 1.9.1 or higher'); + } +}(window.jQuery)); + +(function($) { var BootstrapValidator = function(form, options) { this.$form = $(form); this.options = $.extend({}, $.fn.bootstrapValidator.DEFAULT_OPTIONS, options); this.$invalidFields = $([]); // Array of invalid fields @@ -56,10 +64,11 @@ * Init form */ _init: function() { var that = this, options = { + autoFocus: this.$form.attr('data-bv-autofocus'), container: this.$form.attr('data-bv-container'), events: { formInit: this.$form.attr('data-bv-events-form-init'), formError: this.$form.attr('data-bv-events-form-error'), formSuccess: this.$form.attr('data-bv-events-form-success'), @@ -174,33 +183,37 @@ _parseOptions: function($field) { var field = $field.attr('name') || $field.attr('data-bv-field'), validators = {}, validator, v, // Validator name + attrName, enabled, optionName, + optionAttrName, optionValue, html5AttrName, html5AttrMap; for (v in $.fn.bootstrapValidator.validators) { validator = $.fn.bootstrapValidator.validators[v]; - enabled = $field.attr('data-bv-' + v.toLowerCase()) + ''; + attrName = 'data-bv-' + v.toLowerCase(), + enabled = $field.attr(attrName) + ''; html5AttrMap = ('function' === typeof validator.enableByHtml5) ? validator.enableByHtml5($field) : null; if ((html5AttrMap && enabled !== 'false') - || (html5AttrMap !== true && ('' === enabled || 'true' === enabled))) + || (html5AttrMap !== true && ('' === enabled || 'true' === enabled || attrName === enabled.toLowerCase()))) { // Try to parse the options via attributes validator.html5Attributes = $.extend({}, { message: 'message', onerror: 'onError', onsuccess: 'onSuccess' }, validator.html5Attributes); validators[v] = $.extend({}, html5AttrMap === true ? {} : html5AttrMap, validators[v]); for (html5AttrName in validator.html5Attributes) { optionName = validator.html5Attributes[html5AttrName]; - optionValue = $field.attr('data-bv-' + v.toLowerCase() + '-' + html5AttrName); + optionAttrName = 'data-bv-' + v.toLowerCase() + '-' + html5AttrName, + optionValue = $field.attr(optionAttrName); if (optionValue) { - if ('true' === optionValue) { + if ('true' === optionValue || optionAttrName === optionValue.toLowerCase()) { optionValue = true; } else if ('false' === optionValue) { optionValue = false; } validators[v][optionName] = optionValue; @@ -208,10 +221,11 @@ } } } var opts = { + autoFocus: $field.attr('data-bv-autofocus'), container: $field.attr('data-bv-container'), excluded: $field.attr('data-bv-excluded'), feedbackIcons: $field.attr('data-bv-feedbackicons'), group: $field.attr('data-bv-group'), message: $field.attr('data-bv-message'), @@ -362,36 +376,44 @@ // Fix feedback icons in input-group if ($parent.find('.input-group').length !== 0) { $icon.addClass('bv-icon-input-group') .insertAfter($parent.find('.input-group').eq(0)); } + + // Store the icon as a data of field element + if (!updateAll) { + $field.data('bv.icon', $icon); + } else if (i === total - 1) { + // All fields with the same name have the same icon + fields.data('bv.icon', $icon); + } if (container) { $field // Show tooltip/popover message when field gets focus - .off('focus.bv') - .on('focus.bv', function() { + .off('focus.container.bv') + .on('focus.container.bv', function() { switch (container) { case 'tooltip': - $icon.tooltip('show'); + $(this).data('bv.icon').tooltip('show'); break; case 'popover': - $icon.popover('show'); + $(this).data('bv.icon').popover('show'); break; default: break; } }) // and hide them when losing focus - .off('blur.bv') - .on('blur.bv', function() { + .off('blur.container.bv') + .on('blur.container.bv', function() { switch (container) { case 'tooltip': - $icon.tooltip('hide'); + $(this).data('bv.icon').tooltip('hide'); break; case 'popover': - $icon.popover('hide'); + $(this).data('bv.icon').popover('hide'); break; default: break; } }); @@ -632,20 +654,25 @@ } })(field); } } - var $invalidField = this.$invalidFields.eq(0); - if ($invalidField) { - // Activate the tab containing the invalid field if exists - var $tabPane = $invalidField.parents('.tab-pane'), tabId; - if ($tabPane && (tabId = $tabPane.attr('id'))) { - $('a[href="#' + tabId + '"][data-toggle="tab"]').tab('show'); - } + // Determined the first invalid field which will be focused on automatically + for (var i = 0; i < this.$invalidFields.length; i++) { + var $field = this.$invalidFields.eq(i), + autoFocus = this._isOptionEnabled($field.attr('data-bv-field'), 'autoFocus'); + if (autoFocus) { + // Activate the tab containing the field if exists + var $tabPane = $field.parents('.tab-pane'), tabId; + if ($tabPane && (tabId = $tabPane.attr('id'))) { + $('a[href="#' + tabId + '"][data-toggle="tab"]').tab('show'); + } - // Focus to the first invalid field - $invalidField.focus(); + // Focus the field + $field.focus(); + break; + } } }, /** * The default handler of success.form.bv event. @@ -717,18 +744,35 @@ this.$invalidFields = this.$invalidFields.not($field); $field.trigger($.Event(this.options.events.fieldSuccess), data); } // If all validators are completed and there is at least one validator which doesn't pass - else if (counter[this.STATUS_NOT_VALIDATED] === 0 && counter[this.STATUS_VALIDATING] === 0 && counter[this.STATUS_INVALID] > 0) { + else if ((counter[this.STATUS_NOT_VALIDATED] === 0 || !this._isOptionEnabled(field, 'verbose')) && counter[this.STATUS_VALIDATING] === 0 && counter[this.STATUS_INVALID] > 0) { // Add to the list of invalid fields this.$invalidFields = this.$invalidFields.add($field); $field.trigger($.Event(this.options.events.fieldError), data); } }, + /** + * Check whether or not a field option is enabled + * + * @param {String} field The field name + * @param {String} option The option name, "verbose", "autoFocus", for example + * @returns {Boolean} + */ + _isOptionEnabled: function(field, option) { + if (this.options.fields[field] && (this.options.fields[field][option] === 'true' || this.options.fields[field][option] === true)) { + return true; + } + if (this.options.fields[field] && (this.options.fields[field][option] === 'false' || this.options.fields[field][option] === false)) { + return false; + } + return this.options[option] === 'true' || this.options[option] === true; + }, + // --- // Public methods // --- /** @@ -755,11 +799,11 @@ * @param {String} [option] The option name * @return {String|Object} */ getOptions: function(field, validator, option) { if (!field) { - return this.options; + return option ? this.options[option] : this.options; } if ('object' === typeof field) { field = field.attr('data-bv-field'); } if (!this.options.fields[field]) { @@ -775,11 +819,10 @@ } return option ? options.validators[validator][option] : options.validators[validator]; }, - /** * Disable/enable submit buttons * * @param {Boolean} disabled Can be true or false * @returns {BootstrapValidator} @@ -804,15 +847,17 @@ if (!this.options.fields) { return this; } this.disableSubmitButtons(true); + this._submitIfValid = false; for (var field in this.options.fields) { this.validateField(field); } this._submit(); + this._submitIfValid = true; return this; }, /** @@ -833,20 +878,20 @@ break; default: break; } - if (fields.length === 0 || (this.options.fields[field] && this.options.fields[field].enabled === false)) { + if (fields.length === 0 || !this.options.fields[field] || this.options.fields[field].enabled === false) { return this; } var that = this, type = fields.attr('type'), total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length, updateAll = ('radio' === type || 'checkbox' === type), validators = this.options.fields[field].validators, - verbose = this.options.fields[field].verbose === 'true' || this.options.fields[field].verbose === true || this.options.verbose === 'true' || this.options.verbose === true, + verbose = this._isOptionEnabled(field, 'verbose'), validatorName, validateResult; for (var i = 0; i < total; i++) { var $field = fields.eq(i); @@ -970,10 +1015,11 @@ break; } if (status === this.STATUS_NOT_VALIDATED) { // Reset the flag + // To prevent the form from doing submit when a deferred validator returns true while typing this._submitIfValid = false; } var that = this, type = fields.attr('type'), @@ -988,11 +1034,11 @@ var $parent = $field.parents(group), $message = $field.data('bv.messages'), $allErrors = $message.find('.help-block[data-bv-validator][data-bv-for="' + field + '"]'), $errors = validatorName ? $allErrors.filter('[data-bv-validator="' + validatorName + '"]') : $allErrors, - $icon = $parent.find('.form-control-feedback[data-bv-icon-for="' + field + '"]'), + $icon = $field.data('bv.icon'), container = ('function' === typeof (this.options.fields[field].container || this.options.container)) ? (this.options.fields[field].container || this.options.container).call(this, $field, this) : (this.options.fields[field].container || this.options.container), isValidField = null; // Update status if (validatorName) { @@ -1079,26 +1125,26 @@ case ($icon && 'tooltip' === container): (isValidField === false) ? $icon.css('cursor', 'pointer').tooltip('destroy').tooltip({ container: 'body', html: true, - placement: 'top', + placement: 'auto top', title: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html() }) - : $icon.tooltip('hide'); + : $icon.css('cursor', '').tooltip('destroy'); break; // ... or popover case ($icon && 'popover' === container): (isValidField === false) ? $icon.css('cursor', 'pointer').popover('destroy').popover({ container: 'body', content: $allErrors.filter('[data-bv-result="' + that.STATUS_INVALID + '"]').eq(0).html(), html: true, - placement: 'top', + placement: 'auto top', trigger: 'hover click' }) - : $icon.popover('hide'); + : $icon.css('cursor', '').popover('destroy'); break; default: (status === this.STATUS_INVALID) ? $errors.show() : $errors.hide(); break; } @@ -1148,11 +1194,11 @@ fields = this.getFieldElements(field); break; default: break; } - if (fields.length === 0 || this.options.fields[field] === null || this.options.fields[field].enabled === false) { + if (fields.length === 0 || !this.options.fields[field] || this.options.fields[field].enabled === false) { return true; } var type = fields.attr('type'), total = ('radio' === type || 'checkbox' === type) ? 1 : fields.length, @@ -1607,11 +1653,11 @@ // Turn off events .off('.bv') .removeAttr('data-bv-field'); // Remove feedback icons, tooltip/popover container - $icon = $field.parents(group).find('i[data-bv-icon-for="' + field + '"]'); + $icon = $field.data('bv.icon'); if ($icon) { var container = ('function' === typeof (this.options.fields[field].container || this.options.container)) ? (this.options.fields[field].container || this.options.container).call(this, $field, this) : (this.options.fields[field].container || this.options.container); switch (container) { case 'tooltip': $icon.tooltip('destroy').remove(); @@ -1622,10 +1668,11 @@ default: $icon.remove(); break; } } + $field.removeData('bv.icon'); for (validator in this.options.fields[field].validators) { if ($field.data('bv.dfs.' + validator)) { $field.data('bv.dfs.' + validator).reject(); } @@ -1672,33 +1719,42 @@ } }); }; // The default options + // Sorted in alphabetical order $.fn.bootstrapValidator.DEFAULT_OPTIONS = { - // The form CSS class - elementClass: 'bv-form', + // The first invalid field will be focused automatically + autoFocus: true, - // Default invalid message - message: 'This value is not valid', - - // The CSS selector for indicating the element consists the field - // By default, each field is placed inside the <div class="form-group"></div> - // You should adjust this option if your form group consists of many fields which not all of them need to be validated - group: '.form-group', - //The error messages container. It can be: // - 'tooltip' if you want to use Bootstrap tooltip to show error messages // - 'popover' if you want to use Bootstrap popover to show error messages // - a CSS selector indicating the container // In the first two cases, since the tooltip/popover should be small enough, the plugin only shows only one error message // You also can define the message container for particular field container: null, - // The field will not be live validated if its length is less than this number of characters - threshold: null, + // The form CSS class + elementClass: 'bv-form', + // Use custom event name to avoid window.onerror being invoked by jQuery + // See https://github.com/nghuuphuoc/bootstrapvalidator/issues/630 + events: { + formInit: 'init.form.bv', + formError: 'error.form.bv', + formSuccess: 'success.form.bv', + fieldAdded: 'added.field.bv', + fieldRemoved: 'removed.field.bv', + fieldInit: 'init.field.bv', + fieldError: 'error.field.bv', + fieldSuccess: 'success.field.bv', + fieldStatus: 'status.field.bv', + validatorError: 'error.validator.bv', + validatorSuccess: 'success.validator.bv' + }, + // Indicate fields which won't be validated // By default, the plugin will not validate the following kind of fields: // - disabled // - hidden // - invisible @@ -1744,40 +1800,35 @@ valid: null, invalid: null, validating: null }, - // The submit buttons selector - // These buttons will be disabled to prevent the valid form from multiple submissions - submitButtons: '[type="submit"]', + // Map the field name with validator rules + fields: null, + // The CSS selector for indicating the element consists the field + // By default, each field is placed inside the <div class="form-group"></div> + // You should adjust this option if your form group consists of many fields which not all of them need to be validated + group: '.form-group', + // Live validating option // Can be one of 3 values: // - enabled: The plugin validates fields as soon as they are changed // - disabled: Disable the live validating. The error messages are only shown after the form is submitted // - submitted: The live validating is enabled after the form is submitted live: 'enabled', - // Map the field name with validator rules - fields: null, + // Default invalid message + message: 'This value is not valid', - // Use custom event name to avoid window.onerror being invoked by jQuery - // See https://github.com/nghuuphuoc/bootstrapvalidator/issues/630 - events: { - formInit: 'init.form.bv', - formError: 'error.form.bv', - formSuccess: 'success.form.bv', - fieldAdded: 'added.field.bv', - fieldRemoved: 'removed.field.bv', - fieldInit: 'init.field.bv', - fieldError: 'error.field.bv', - fieldSuccess: 'success.field.bv', - fieldStatus: 'status.field.bv', - validatorError: 'error.validator.bv', - validatorSuccess: 'success.validator.bv' - }, - + // The submit buttons selector + // These buttons will be disabled to prevent the valid form from multiple submissions + submitButtons: '[type="submit"]', + + // The field will not be live validated if its length is less than this number of characters + threshold: null, + // Whether to be verbose when validating a field or not. // Possible values: // - true: when a field has multiple validators, all of them will be checked, and respectively - if errors occur in // multiple validators, all of them will be displayed to the user // - false: when a field has multiple validators, validation for this field will be terminated upon the first encountered error. @@ -2022,26 +2073,35 @@ validate: function(validator, $field, options) { var value = $field.val(); if (value === '') { return true; } + + value = this._format(value); if (!$.isNumeric(value)) { return false; } - var min = $.isNumeric(options.min) ? options.min : validator.getDynamicOption($field, options.min), - max = $.isNumeric(options.max) ? options.max : validator.getDynamicOption($field, options.max); + var min = $.isNumeric(options.min) ? options.min : validator.getDynamicOption($field, options.min), + max = $.isNumeric(options.max) ? options.max : validator.getDynamicOption($field, options.max), + minValue = this._format(min), + maxValue = this._format(max); + value = parseFloat(value); return (options.inclusive === true || options.inclusive === undefined) ? { - valid: value >= min && value <= max, + valid: value >= minValue && value <= maxValue, message: $.fn.bootstrapValidator.helpers.format(options.message || $.fn.bootstrapValidator.i18n.between['default'], [min, max]) } : { - valid: value > min && value < max, + valid: value > minValue && value < maxValue, message: $.fn.bootstrapValidator.helpers.format(options.message || $.fn.bootstrapValidator.i18n.between.notInclusive, [min, max]) }; + }, + + _format: function(value) { + return (value + '').replace(',', '.'); } }; }(window.jQuery)); ;(function($) { $.fn.bootstrapValidator.validators.blank = { @@ -2180,10 +2240,139 @@ return { valid: isValid, message: message }; } }; }(window.jQuery)); ;(function($) { + $.fn.bootstrapValidator.i18n.color = $.extend($.fn.bootstrapValidator.i18n.color || {}, { + 'default': 'Please enter a valid color' + }); + + $.fn.bootstrapValidator.validators.color = { + SUPPORTED_TYPES: [ + 'hex', 'rgb', 'rgba', 'hsl', 'hsla', 'keyword' + ], + + KEYWORD_COLORS: [ + // Colors start with A + 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', + // B + 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', + // C + 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', + // D + 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta', + 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', + 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', + 'dimgrey', 'dodgerblue', + // F + 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', + // G + 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey', + // H + 'honeydew', 'hotpink', + // I + 'indianred', 'indigo', 'ivory', + // K + 'khaki', + // L + 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', + 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', + 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', + 'linen', + // M + 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', + 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', + 'mistyrose', 'moccasin', + // N + 'navajowhite', 'navy', + // O + 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', + // P + 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', + 'plum', 'powderblue', 'purple', + // R + 'red', 'rosybrown', 'royalblue', + // S + 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', + 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', + // T + 'tan', 'teal', 'thistle', 'tomato', 'transparent', 'turquoise', + // V + 'violet', + // W + 'wheat', 'white', 'whitesmoke', + // Y + 'yellow', 'yellowgreen' + ], + + /** + * Return true if the input value is a valid color + * + * @param {BootstrapValidator} validator The validator plugin instance + * @param {jQuery} $field Field element + * @param {Object} options Can consist of the following keys: + * - message: The invalid message + * - type: The array of valid color types + * @returns {Boolean} + */ + validate: function(validator, $field, options) { + var value = $field.val(); + if (value === '') { + return true; + } + + var types = options.type || this.SUPPORTED_TYPES; + if (!$.isArray(types)) { + types = types.replace(/s/g, '').split(','); + } + + var method, + type, + isValid = false; + + for (var i = 0; i < types.length; i++) { + type = types[i]; + method = '_' + type.toLowerCase(); + isValid = isValid || this[method](value); + if (isValid) { + return true; + } + } + + return false; + }, + + _hex: function(value) { + return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value); + }, + + _hsl: function(value) { + return /^hsl\((\s*(-?\d+)\s*,)(\s*(\b(0?\d{1,2}|100)\b%)\s*,)(\s*(\b(0?\d{1,2}|100)\b%)\s*)\)$/.test(value); + }, + + _hsla: function(value) { + return /^hsla\((\s*(-?\d+)\s*,)(\s*(\b(0?\d{1,2}|100)\b%)\s*,){2}(\s*(0?(\.\d+)?|1(\.0+)?)\s*)\)$/.test(value); + }, + + _keyword: function(value) { + return $.inArray(value, this.KEYWORD_COLORS) >= 0; + }, + + _rgb: function(value) { + var regexInteger = /^rgb\((\s*(\b([01]?\d{1,2}|2[0-4]\d|25[0-5])\b)\s*,){2}(\s*(\b([01]?\d{1,2}|2[0-4]\d|25[0-5])\b)\s*)\)$/, + regexPercent = /^rgb\((\s*(\b(0?\d{1,2}|100)\b%)\s*,){2}(\s*(\b(0?\d{1,2}|100)\b%)\s*)\)$/; + return regexInteger.test(value) || regexPercent.test(value); + }, + + _rgba: function(value) { + var regexInteger = /^rgba\((\s*(\b([01]?\d{1,2}|2[0-4]\d|25[0-5])\b)\s*,){3}(\s*(0?(\.\d+)?|1(\.0+)?)\s*)\)$/, + regexPercent = /^rgba\((\s*(\b(0?\d{1,2}|100)\b%)\s*,){3}(\s*(0?(\.\d+)?|1(\.0+)?)\s*)\)$/; + return regexInteger.test(value) || regexPercent.test(value); + } + }; +}(window.jQuery)); +;(function($) { $.fn.bootstrapValidator.i18n.creditCard = $.extend($.fn.bootstrapValidator.i18n.creditCard || {}, { 'default': 'Please enter a valid credit card number' }); $.fn.bootstrapValidator.validators.creditCard = { @@ -2455,37 +2644,44 @@ } }; }(window.jQuery)); ;(function($) { $.fn.bootstrapValidator.i18n.date = $.extend($.fn.bootstrapValidator.i18n.date || {}, { - 'default': 'Please enter a valid date' + 'default': 'Please enter a valid date', + min: 'Please enter a date after %s', + max: 'Please enter a date before %s', + range: 'Please enter a date in the range %s - %s' }); $.fn.bootstrapValidator.validators.date = { html5Attributes: { message: 'message', format: 'format', + min: 'min', + max: 'max', separator: 'separator' }, /** * Return true if the input value is valid date * * @param {BootstrapValidator} validator The validator plugin instance * @param {jQuery} $field Field element * @param {Object} options Can consist of the following keys: * - message: The invalid message + * - min: the minimum date + * - max: the maximum date * - separator: Use to separate the date, month, and year. * By default, it is / * - format: The date format. Default is MM/DD/YYYY * The format can be: * * i) date: Consist of DD, MM, YYYY parts which are separated by the separator option * ii) date and time: * The time can consist of h, m, s parts which are separated by : * ii) date, time and A (indicating AM or PM) - * @returns {Boolean} + * @returns {Boolean|Object} */ validate: function(validator, $field, options) { var value = $field.val(); if (value === '') { return true; @@ -2505,87 +2701,200 @@ sections = value.split(' '), date = sections[0], time = (sections.length > 1) ? sections[1] : null; if (formats.length !== sections.length) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } // Determine the separator var separator = options.separator; if (!separator) { separator = (date.indexOf('/') !== -1) ? '/' : ((date.indexOf('-') !== -1) ? '-' : null); } if (separator === null || date.indexOf(separator) === -1) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } // Determine the date date = date.split(separator); dateFormat = dateFormat.split(separator); if (date.length !== dateFormat.length) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } var year = date[$.inArray('YYYY', dateFormat)], month = date[$.inArray('MM', dateFormat)], day = date[$.inArray('DD', dateFormat)]; if (!year || !month || !day || year.length !== 4) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } // Determine the time var minutes = null, hours = null, seconds = null; if (timeFormat) { timeFormat = timeFormat.split(':'); time = time.split(':'); if (timeFormat.length !== time.length) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } hours = time.length > 0 ? time[0] : null; minutes = time.length > 1 ? time[1] : null; seconds = time.length > 2 ? time[2] : null; // Validate seconds if (seconds) { if (isNaN(seconds) || seconds.length > 2) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } seconds = parseInt(seconds, 10); if (seconds < 0 || seconds > 60) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } } // Validate hours if (hours) { if (isNaN(hours) || hours.length > 2) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } hours = parseInt(hours, 10); if (hours < 0 || hours >= 24 || (amOrPm && hours > 12)) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } } // Validate minutes if (minutes) { if (isNaN(minutes) || minutes.length > 2) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } minutes = parseInt(minutes, 10); if (minutes < 0 || minutes > 59) { - return false; + return { + valid: false, + message: options.message || $.fn.bootstrapValidator.i18n.date['default'] + }; } } } // Validate day, month, and year - return $.fn.bootstrapValidator.helpers.date(year, month, day); + var valid = $.fn.bootstrapValidator.helpers.date(year, month, day), + message = options.message || $.fn.bootstrapValidator.i18n.date['default']; + + // declare the date, min and max objects + var min = null, + max = null, + minOption = options.min, + maxOption = options.max; + + if (minOption) { + if (isNaN(Date.parse(minOption))) { + minOption = validator.getDynamicOption($field, minOption); + } + min = this._parseDate(minOption, dateFormat, separator); + } + + if (maxOption) { + if (isNaN(Date.parse(maxOption))) { + maxOption = validator.getDynamicOption($field, maxOption); + } + max = this._parseDate(maxOption, dateFormat, separator); + } + + date = new Date(year, month, day, hours, minutes, seconds); + + switch (true) { + case (minOption && !maxOption && valid): + valid = date.getTime() >= min.getTime(); + message = options.message || $.fn.bootstrapValidator.helpers.format($.fn.bootstrapValidator.i18n.date.min, minOption); + break; + + case (maxOption && !minOption && valid): + valid = date.getTime() <= max.getTime(); + message = options.message || $.fn.bootstrapValidator.helpers.format($.fn.bootstrapValidator.i18n.date.max, maxOption); + break; + + case (maxOption && minOption && valid): + valid = date.getTime() <= max.getTime() && date.getTime() >= min.getTime(); + message = options.message || $.fn.bootstrapValidator.helpers.format($.fn.bootstrapValidator.i18n.date.range, [minOption, maxOption]); + break; + + default: + break; + } + + return { + valid: valid, + message: message + }; + }, + + /** + * Return a date object after parsing the date string + * + * @param {String} date The date string to parse + * @param {String} format The date format + * The format can be: + * - date: Consist of DD, MM, YYYY parts which are separated by the separator option + * - date and time: + * The time can consist of h, m, s parts which are separated by : + * @param {String} separator The separator used to separate the date, month, and year + * @returns {Date} + */ + _parseDate: function(date, format, separator) { + var minutes = 0, hours = 0, seconds = 0, + sections = date.split(' '), + dateSection = sections[0], + timeSection = (sections.length > 1) ? sections[1] : null; + + dateSection = dateSection.split(separator); + var year = dateSection[$.inArray('YYYY', format)], + month = dateSection[$.inArray('MM', format)], + day = dateSection[$.inArray('DD', format)]; + if (timeSection) { + timeSection = timeSection.split(':'); + hours = timeSection.length > 0 ? timeSection[0] : null; + minutes = timeSection.length > 1 ? timeSection[1] : null; + seconds = timeSection.length > 2 ? timeSection[2] : null; + } + + return new Date(year, month, day, hours, minutes, seconds); } }; }(window.jQuery)); ;(function($) { $.fn.bootstrapValidator.i18n.different = $.extend($.fn.bootstrapValidator.i18n.different || {}, { @@ -2792,12 +3101,16 @@ }); $.fn.bootstrapValidator.validators.file = { html5Attributes: { extension: 'extension', + maxfiles: 'maxFiles', + minfiles: 'minFiles', maxsize: 'maxSize', minsize: 'minSize', + maxtotalsize: 'maxTotalSize', + mintotalsize: 'minTotalSize', message: 'message', type: 'type' }, /** @@ -2805,12 +3118,16 @@ * * @param {BootstrapValidator} validator The validator plugin instance * @param {jQuery} $field Field element * @param {Object} options Can consist of the following keys: * - extension: The allowed extensions, separated by a comma + * - maxFiles: The maximum number of files + * - minFiles: The minimum number of files * - maxSize: The maximum size in bytes - * - minSize: the minimum size in bytes + * - minSize: The minimum size in bytes + * - maxTotalSize: The maximum size in bytes for all files + * - minTotalSize: The minimum size in bytes for all files * - message: The invalid message * - type: The allowed MIME type, separated by a comma * @returns {Boolean} */ validate: function(validator, $field, options) { @@ -2824,33 +3141,37 @@ types = options.type ? options.type.toLowerCase().split(',') : null, html5 = (window.File && window.FileList && window.FileReader); if (html5) { // Get FileList instance - var files = $field.get(0).files, - total = files.length; + var files = $field.get(0).files, + total = files.length, + totalSize = 0; + + if ((options.maxFiles && total > parseInt(options.maxFiles, 10)) // Check the maxFiles + || (options.minFiles && total < parseInt(options.minFiles, 10))) // Check the minFiles + { + return false; + } + for (var i = 0; i < total; i++) { - // Check the minSize - if (options.minSize && files[i].size < parseInt(options.minSize, 10)) { - return false; - } - - // Check the maxSize - if (options.maxSize && files[i].size > parseInt(options.maxSize, 10)) { - return false; - } + totalSize += files[i].size; + ext = files[i].name.substr(files[i].name.lastIndexOf('.') + 1); - // Check file extension - ext = files[i].name.substr(files[i].name.lastIndexOf('.') + 1); - if (extensions && $.inArray(ext.toLowerCase(), extensions) === -1) { + if ((options.minSize && files[i].size < parseInt(options.minSize, 10)) // Check the minSize + || (options.maxSize && files[i].size > parseInt(options.maxSize, 10)) // Check the maxSize + || (extensions && $.inArray(ext.toLowerCase(), extensions) === -1) // Check file extension + || (files[i].type && types && $.inArray(files[i].type.toLowerCase(), types) === -1)) // Check file type + { return false; } + } - // Check file type - if (files[i].type && types && $.inArray(files[i].type.toLowerCase(), types) === -1) { - return false; - } + if ((options.maxTotalSize && totalSize > parseInt(options.maxTotalSize, 10)) // Check the maxTotalSize + || (options.minTotalSize && totalSize < parseInt(options.minTotalSize, 10))) // Check the minTotalSize + { + return false; } } else { // Check file extension ext = value.substr(value.lastIndexOf('.') + 1); if (extensions && $.inArray(ext.toLowerCase(), extensions) === -1) { @@ -2906,25 +3227,33 @@ validate: function(validator, $field, options) { var value = $field.val(); if (value === '') { return true; } + + value = this._format(value); if (!$.isNumeric(value)) { return false; } - var compareTo = $.isNumeric(options.value) ? options.value : validator.getDynamicOption($field, options.value); + var compareTo = $.isNumeric(options.value) ? options.value : validator.getDynamicOption($field, options.value), + compareToValue = this._format(compareTo); + value = parseFloat(value); return (options.inclusive === true || options.inclusive === undefined) ? { - valid: value >= compareTo, + valid: value >= compareToValue, message: $.fn.bootstrapValidator.helpers.format(options.message || $.fn.bootstrapValidator.i18n.greaterThan['default'], compareTo) } : { - valid: value > compareTo, + valid: value > compareToValue, message: $.fn.bootstrapValidator.helpers.format(options.message || $.fn.bootstrapValidator.i18n.greaterThan.notInclusive, compareTo) }; + }, + + _format: function(value) { + return (value + '').replace(',', '.'); } }; }(window.jQuery)); ;(function($) { $.fn.bootstrapValidator.i18n.grid = $.extend($.fn.bootstrapValidator.i18n.grid || {}, { @@ -3010,11 +3339,16 @@ validate: function(validator, $field, options) { var value = $field.val(); if (value === '') { return true; } - return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value); + + return ('color' === $field.attr('type')) + // Only accept 6 hex character values due to the HTML 5 spec + // See http://www.w3.org/TR/html-markup/input.color.html#input.color.attrs.value + ? /^#[0-9A-F]{6}$/i.test(value) + : /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value); } }; }(window.jQuery)); ;(function($) { $.fn.bootstrapValidator.i18n.iban = $.extend($.fn.bootstrapValidator.i18n.iban || {}, { @@ -3043,11 +3377,11 @@ CV: 'Cape Verde', CY: 'Cyprus', CZ: 'Czech Republic', DE: 'Germany', DK: 'Denmark', - DO: 'Dominica', + DO: 'Dominican Republic', DZ: 'Algeria', EE: 'Estonia', ES: 'Spain', FI: 'Finland', FO: 'Faroe Islands', @@ -5155,25 +5489,33 @@ validate: function(validator, $field, options) { var value = $field.val(); if (value === '') { return true; } + + value = this._format(value); if (!$.isNumeric(value)) { return false; } - var compareTo = $.isNumeric(options.value) ? options.value : validator.getDynamicOption($field, options.value); + var compareTo = $.isNumeric(options.value) ? options.value : validator.getDynamicOption($field, options.value), + compareToValue = this._format(compareTo); + value = parseFloat(value); return (options.inclusive === true || options.inclusive === undefined) ? { - valid: value <= compareTo, + valid: value <= compareToValue, message: $.fn.bootstrapValidator.helpers.format(options.message || $.fn.bootstrapValidator.i18n.lessThan['default'], compareTo) } : { - valid: value < compareTo, + valid: value < compareToValue, message: $.fn.bootstrapValidator.helpers.format(options.message || $.fn.bootstrapValidator.i18n.lessThan.notInclusive, compareTo) }; + }, + + _format: function(value) { + return (value + '').replace(',', '.'); } }; }(window.jQuery)); ;(function($) { $.fn.bootstrapValidator.i18n.mac = $.extend($.fn.bootstrapValidator.i18n.mac || {}, { @@ -5369,10 +5711,11 @@ country: 'Please enter a valid phone number in %s', countries: { BR: 'Brazil', CN: 'China', CZ: 'Czech Republic', + DE: 'Germany', DK: 'Denmark', ES: 'Spain', FR: 'France', GB: 'United Kingdom', MA: 'Morocco', @@ -5391,11 +5734,11 @@ message: 'message', country: 'country' }, // The supported countries - COUNTRY_CODES: ['BR', 'CN', 'CZ', 'DK', 'ES', 'FR', 'GB', 'MA', 'PK', 'RO', 'RU', 'SK', 'TH', 'US', 'VE'], + COUNTRY_CODES: ['BR', 'CN', 'CZ', 'DE', 'DK', 'ES', 'FR', 'GB', 'MA', 'PK', 'RO', 'RU', 'SK', 'TH', 'US', 'VE'], /** * Return true if the input value contains a valid phone number for the country * selected in the options * @@ -5447,10 +5790,16 @@ case 'CZ': // Test: http://regexr.com/39hhl isValid = /^(((00)([- ]?)|\+)(420)([- ]?))?((\d{3})([- ]?)){2}(\d{3})$/.test(value); break; + case 'DE': + // Test: http://regexr.com/39pkg + value = $.trim(value); + isValid = (/^(((((((00|\+)49[ \-/]?)|0)[1-9][0-9]{1,4})[ \-/]?)|((((00|\+)49\()|\(0)[1-9][0-9]{1,4}\)[ \-/]?))[0-9]{1,7}([ \-/]?[0-9]{1,5})?)$/).test(value); + break; + case 'DK': // Mathing DK phone numbers with country code in 1 of 3 formats and an // 8 digit phone number not starting with a 0 or 1. Can have 1 space // between each character except inside the country code. // Test: http://regex101.com/r/sS8fO4/1 @@ -5587,10 +5936,11 @@ html5Attributes: { message: 'message', name: 'name', type: 'type', url: 'url', + data: 'data', delay: 'delay' }, /** * Destroy the timer when destroying the bootstrapValidator (using validator.destroy() method) @@ -5637,10 +5987,15 @@ // Support dynamic data if ('function' === typeof data) { data = data.call(this, validator); } + // Parse string data from HTML5 attribute + if ('string' === typeof data) { + data = JSON.parse(data); + } + // Support dynamic url if ('function' === typeof url) { url = url.call(this, validator); } @@ -5934,11 +6289,13 @@ $.fn.bootstrapValidator.validators.stringLength = { html5Attributes: { message: 'message', min: 'min', - max: 'max' + max: 'max', + trim: 'trim', + utf8bytes: 'utf8Bytes' }, enableByHtml5: function($field) { var options = {}, maxLength = $field.attr('maxlength'), @@ -5967,23 +6324,45 @@ * - Name of field which its value defines the number * - Name of callback function that returns the number * - A callback function that returns the number * * - message: The invalid message + * - trim: Indicate the length will be calculated after trimming the value or not. It is false, by default + * - utf8bytes: Evaluate string length in UTF-8 bytes, default to false * @returns {Object} */ validate: function(validator, $field, options) { var value = $field.val(); + if (options.trim === true || options.trim === 'true') { + value = $.trim(value); + } + if (value === '') { return true; } - var min = $.isNumeric(options.min) ? options.min : validator.getDynamicOption($field, options.min), - max = $.isNumeric(options.max) ? options.max : validator.getDynamicOption($field, options.max), - length = value.length, - isValid = true, - message = options.message || $.fn.bootstrapValidator.i18n.stringLength['default']; + var min = $.isNumeric(options.min) ? options.min : validator.getDynamicOption($field, options.min), + max = $.isNumeric(options.max) ? options.max : validator.getDynamicOption($field, options.max), + // Credit to http://stackoverflow.com/a/23329386 (@lovasoa) for UTF-8 byte length code + utf8Length = function(str) { + var s = str.length; + for (var i = str.length - 1; i >= 0; i--) { + var code = str.charCodeAt(i); + if (code > 0x7f && code <= 0x7ff) { + s++; + } else if (code > 0x7ff && code <= 0xffff) { + s += 2; + } + if (code >= 0xDC00 && code <= 0xDFFF) { + i--; + } + } + return s; + }, + length = options.utf8Bytes ? utf8Length(value) : value.length, + isValid = true, + message = options.message || $.fn.bootstrapValidator.i18n.stringLength['default']; if ((min && length < parseInt(min, 10)) || (max && length > parseInt(max, 10))) { isValid = false; } @@ -7633,18 +8012,24 @@ $.fn.bootstrapValidator.i18n.zipCode = $.extend($.fn.bootstrapValidator.i18n.zipCode || {}, { 'default': 'Please enter a valid postal code', countryNotSupported: 'The country code %s is not supported', country: 'Please enter a valid postal code in %s', countries: { + AT: 'Austria', BR: 'Brazil', CA: 'Canada', + CH: 'Switzerland', CZ: 'Czech Republic', + DE: 'Germany', DK: 'Denmark', + FR: 'France', GB: 'United Kingdom', + IE: 'Ireland', IT: 'Italy', MA: 'Morocco', NL: 'Netherlands', + PT: 'Portugal', RO: 'Romania', RU: 'Russia', SE: 'Sweden', SG: 'Singapore', SK: 'Slovakia', @@ -7656,11 +8041,11 @@ html5Attributes: { message: 'message', country: 'country' }, - COUNTRY_CODES: ['BR', 'CA', 'CZ', 'DK', 'GB', 'IT', 'MA', 'NL', 'RO', 'RU', 'SE', 'SG', 'SK', 'US'], + COUNTRY_CODES: [ 'AT', 'BR', 'CA', 'CH', 'CZ', 'DE', 'DK', 'FR', 'GB', 'IE', 'IT', 'MA', 'NL', 'PT', 'RO', 'RU', 'SE', 'SG', 'SK', 'US'], /** * Return true if and only if the input value is a valid country zip code * * @param {BootstrapValidator} validator The validator plugin instance @@ -7700,31 +8085,56 @@ } var isValid = false; country = country.toUpperCase(); switch (country) { + // http://en.wikipedia.org/wiki/List_of_postal_codes_in_Austria + case 'AT': + isValid = /^([1-9]{1})(\d{3})$/.test(value); + break; + case 'BR': isValid = /^(\d{2})([\.]?)(\d{3})([\-]?)(\d{3})$/.test(value); break; case 'CA': isValid = /^(?:A|B|C|E|G|H|J|K|L|M|N|P|R|S|T|V|X|Y){1}[0-9]{1}(?:A|B|C|E|G|H|J|K|L|M|N|P|R|S|T|V|W|X|Y|Z){1}\s?[0-9]{1}(?:A|B|C|E|G|H|J|K|L|M|N|P|R|S|T|V|W|X|Y|Z){1}[0-9]{1}$/i.test(value); break; + case 'CH': + isValid = /^([1-9]{1})(\d{3})$/.test(value); + break; + case 'CZ': // Test: http://regexr.com/39hhr isValid = /^(\d{3})([ ]?)(\d{2})$/.test(value); break; + // http://stackoverflow.com/questions/7926687/regular-expression-german-zip-codes + case 'DE': + isValid = /^(?!01000|99999)(0[1-9]\d{3}|[1-9]\d{4})$/.test(value); + break; + case 'DK': isValid = /^(DK(-|\s)?)?\d{4}$/i.test(value); break; + // http://en.wikipedia.org/wiki/Postal_codes_in_France + case 'FR': + isValid = /^[0-9]{5}$/i.test(value); + break; + case 'GB': isValid = this._gb(value); break; + // http://www.eircode.ie/docs/default-source/Common/prepare-your-business-for-eircode---published-v2.pdf?sfvrsn=2 + // Test: http://refiddle.com/1kpl + case 'IE': + isValid = /^(D6W|[ACDEFHKNPRTVWXY]\d{2})\s[0-9ACDEFHKNPRTVWXY]{4}$/.test(value); + break; + // http://en.wikipedia.org/wiki/List_of_postal_codes_in_Italy case 'IT': isValid = /^(I-|IT-)?\d{5}$/i.test(value); break; @@ -7735,11 +8145,16 @@ // http://en.wikipedia.org/wiki/Postal_codes_in_the_Netherlands case 'NL': isValid = /^[1-9][0-9]{3} ?(?!sa|sd|ss)[a-z]{2}$/i.test(value); break; - + + // Test: http://refiddle.com/1l2t + case 'PT': + isValid = /^[1-9]\d{3}-\d{3}$/.test(value); + break; + case 'RO': isValid = /^(0[1-8]{1}|[1-9]{1}[0-5]{1})?[0-9]{4}$/i.test(value); break; case 'RU': @@ -7750,10 +8165,10 @@ isValid = /^(S-)?\d{3}\s?\d{2}$/i.test(value); break; case 'SG': isValid = /^([0][1-9]|[1-6][0-9]|[7]([0-3]|[5-9])|[8][0-2])(\d{4})$/i.test(value); - break; + break; case 'SK': // Test: http://regexr.com/39hhr isValid = /^(\d{3})([ ]?)(\d{2})$/.test(value); break;