/** * BootstrapValidator (https://github.com/nghuuphuoc/bootstrapvalidator) * * A jQuery plugin to validate form fields. Use with Bootstrap 3 * * @author http://twitter.com/nghuuphuoc * @copyright (c) 2013 - 2014 Nguyen Huu Phuoc * @license MIT */ (function($) { var BootstrapValidator = function(form, options) { this.$form = $(form); this.options = $.extend({}, BootstrapValidator.DEFAULT_OPTIONS, options); this.dfds = {}; // Array of deferred this.results = {}; // Validating results this.invalidField = null; // First invalid field this.$submitButton = null; // The submit button which is clicked to submit form this._init(); this.STATUS_NOT_VALIDATED = 'NOT_VALIDATED'; this.STATUS_VALIDATING = 'VALIDATING'; this.STATUS_INVALID = 'INVALID'; this.STATUS_VALID = 'VALID'; }; // The default options BootstrapValidator.DEFAULT_OPTIONS = { // The form CSS class elementClass: 'bootstrap-validator-form', // Default invalid message message: 'This value is not valid', // Shows ok/error/loading icons based on the field validity. // This feature requires Bootstrap v3.1.0 or later (http://getbootstrap.com/css/#forms-control-validation). // Since Bootstrap doesn't provide any methods to know its version, this option cannot be on/off automatically. // In other word, to use this feature you have to upgrade your Bootstrap to v3.1.0 or later. // // Examples: // - Use Glyphicons icons: // feedbackIcons: { // valid: 'glyphicon glyphicon-ok', // invalid: 'glyphicon glyphicon-remove', // validating: 'glyphicon glyphicon-refresh' // } // - Use FontAwesome icons: // feedbackIcons: { // valid: 'fa fa-check', // invalid: 'fa fa-times', // validating: 'fa fa-refresh' // } feedbackIcons: null, // The submit buttons selector // These buttons will be disabled to prevent the valid form from multiple submissions submitButtons: 'button[type="submit"]', // The custom submit handler // It will prevent the form from the default submission // // submitHandler: function(validator, form) { // - validator is the BootstrapValidator instance // - form is the jQuery object present the current form // } submitHandler: null, // 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 }; BootstrapValidator.prototype = { constructor: BootstrapValidator, /** * Init form */ _init: function() { if (this.options.fields == null) { return; } var that = this; this.$form // Disable client side validation in HTML 5 .attr('novalidate', 'novalidate') .addClass(this.options.elementClass) // Disable the default submission first .on('submit.bootstrapValidator', function(e) { e.preventDefault(); that.validate(); }) .find(this.options.submitButtons) .on('click', function() { that.$submitButton = $(this); }); for (var field in this.options.fields) { this._initField(field); } this._setLiveValidating(); }, /** * Init field * * @param {String} field The field name */ _initField: function(field) { if (this.options.fields[field] == null || this.options.fields[field].validators == null) { return; } this.dfds[field] = {}; this.results[field] = {}; var fields = this.getFieldElements(field); // We don't need to validate non-existing fields if (fields == null) { delete this.options.fields[field]; delete this.dfds[field]; return; } // Create help block elements for showing the error messages var $field = $(fields[0]), $parent = $field.parents('.form-group'), $message = this._getMessageContainer($field); $field.data('bootstrapValidator.messageContainer', $message); for (var validatorName in this.options.fields[field].validators) { if (!$.fn.bootstrapValidator.validators[validatorName]) { delete this.options.fields[field].validators[validatorName]; continue; } this.results[field][validatorName] = this.STATUS_NOT_VALIDATED; $('') .css('display', 'none') .attr('data-bs-validator', validatorName) .html(this.options.fields[field].validators[validatorName].message || this.options.message) .addClass('help-block') .appendTo($message); } // Prepare the feedback icons // Available from Bootstrap 3.1 (http://getbootstrap.com/css/#forms-control-validation) if (this.options.feedbackIcons) { $parent.addClass('has-feedback'); var $icon = $('').css('display', 'none').addClass('form-control-feedback').insertAfter($(fields[fields.length - 1])); // The feedback icon does not render correctly if there is no label // https://github.com/twbs/bootstrap/issues/12873 if ($parent.find('label').length == 0) { $icon.css('top', 0); } } if (this.options.fields[field]['enabled'] == null) { this.options.fields[field]['enabled'] = true; } // Whenever the user change the field value, mark it as not validated yet var that = this, type = fields.attr('type'), event = ('radio' == type || 'checkbox' == type || 'SELECT' == fields[0].tagName) ? 'change' : 'keyup'; fields.on(event, function() { that.setNotValidated(field); }); }, /** * Get the element to place the error messages * * @param {jQuery} $field The field element * @returns {jQuery} */ _getMessageContainer: function($field) { var $parent = $field.parent(); if ($parent.hasClass('form-group')) { return $parent; } var cssClasses = $parent.attr('class'); if (!cssClasses) { return this._getMessageContainer($parent); } cssClasses = cssClasses.split(' '); var n = cssClasses.length; for (var i = 0; i < n; i++) { if (/^col-(xs|sm|md|lg)-\d+$/.test(cssClasses[i]) || /^col-(xs|sm|md|lg)-offset-\d+$/.test(cssClasses[i])) { return $parent; } } return this._getMessageContainer($parent); }, /** * Enable live validating */ _setLiveValidating: function() { if ('enabled' == this.options.live) { var that = this; for (var field in this.options.fields) { (function(f) { var fields = that.getFieldElements(f); if (fields) { var type = fields.attr('type'), event = ('radio' == type || 'checkbox' == type || 'SELECT' == fields[0].tagName) ? 'change' : 'keyup'; fields.on(event, function() { that.validateField(f); }); } })(field); } } }, /** * Disable/Enable submit buttons * * @param {Boolean} disabled */ _disableSubmitButtons: function(disabled) { if (!disabled) { this.$form.find(this.options.submitButtons).removeAttr('disabled'); } else if (this.options.live != 'disabled') { // Don't disable if the live validating mode is disabled this.$form.find(this.options.submitButtons).attr('disabled', 'disabled'); } }, /** * Called when all validations are completed */ _submit: function() { if (!this.isValid()) { if ('submitted' == this.options.live) { this.options.live = 'enabled'; this._setLiveValidating(); } // Focus to the first invalid field if (this.invalidField) { this.getFieldElements(this.invalidField).focus(); } return; } this._disableSubmitButtons(true); // Call the custom submission if enabled if (this.options.submitHandler && 'function' == typeof this.options.submitHandler) { this.options.submitHandler.call(this, this, this.$form, this.$submitButton); } else { // Submit form this.$form.off('submit.bootstrapValidator').submit(); } }, // --- Public methods --- /** * Retrieve the field elements by given name * * @param {String} field The field name * @returns {null|jQuery[]} */ getFieldElements: function(field) { var fields = this.$form.find('[name="' + field + '"]'); return (fields.length == 0) ? null : fields; }, /** * Validate the form * * @return {BootstrapValidator} */ validate: function() { if (!this.options.fields) { return this; } this._disableSubmitButtons(true); for (var field in this.options.fields) { this.validateField(field); } this._submit(); return this; }, /** * Validate given field * * @param {String} field The field name */ validateField: function(field) { if (!this.options.fields[field]['enabled']) { return; } var that = this, fields = this.getFieldElements(field), $field = $(fields[0]), validators = this.options.fields[field].validators, validatorName, validateResult; // We don't need to validate disabled field if (fields.length == 1 && fields.is(':disabled')) { delete this.options.fields[field]; delete this.dfds[field]; return; } for (validatorName in validators) { if (this.dfds[field][validatorName]) { this.dfds[field][validatorName].reject(); } // Don't validate field if it is already done if (this.results[field][validatorName] == this.STATUS_VALID || this.results[field][validatorName] == this.STATUS_INVALID) { continue; } this.results[field][validatorName] = this.STATUS_VALIDATING; validateResult = $.fn.bootstrapValidator.validators[validatorName].validate(this, $field, validators[validatorName]); if ('object' == typeof validateResult) { this.updateStatus($field, this.STATUS_VALIDATING, validatorName); this.dfds[field][validatorName] = validateResult; validateResult.done(function(isValid, v) { // v is validator name delete that.dfds[field][v]; that.updateStatus($field, isValid ? that.STATUS_VALID : that.STATUS_INVALID, v); if (isValid && 'disabled' == that.options.live) { that._submit(); } }); } else if ('boolean' == typeof validateResult) { this.updateStatus($field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName); } } }, /** * Check the form validity * * @returns {Boolean} */ isValid: function() { var field, validatorName; for (field in this.results) { if (!this.options.fields[field]['enabled']) { continue; } for (validatorName in this.results[field]) { if (this.results[field][validatorName] == this.STATUS_NOT_VALIDATED || this.results[field][validatorName] == this.STATUS_VALIDATING) { return false; } if (this.results[field][validatorName] == this.STATUS_INVALID) { this.invalidField = field; return false; } } } return true; }, /** * Update field status * * @param {jQuery} $field The field element * @param {String} validatorName The validator name * @param {String} status The status * Can be STATUS_VALIDATING, STATUS_INVALID, STATUS_VALID * @return {BootstrapValidator} */ updateStatus: function($field, status, validatorName) { var that = this, field = $field.attr('name'), $parent = $field.parents('.form-group'), $message = $field.data('bootstrapValidator.messageContainer'), $errors = $message.find('.help-block[data-bs-validator]'); switch (status) { case this.STATUS_VALIDATING: this.results[field][validatorName] = this.STATUS_VALIDATING; this._disableSubmitButtons(true); $parent.removeClass('has-success').removeClass('has-error'); // TODO: Show validating message $errors.filter('.help-block[data-bs-validator="' + validatorName + '"]').hide(); if (this.options.feedbackIcons) { // Show validating icon $message.find('.form-control-feedback').removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).addClass(this.options.feedbackIcons.validating).show(); } break; case this.STATUS_INVALID: this.results[field][validatorName] = this.STATUS_INVALID; this._disableSubmitButtons(true); // Add has-error class to parent element $parent.removeClass('has-success').addClass('has-error'); $errors.filter('[data-bs-validator="' + validatorName + '"]').show(); if (this.options.feedbackIcons) { // Show invalid icon $message.find('.form-control-feedback').removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.validating).addClass(this.options.feedbackIcons.invalid).show(); } break; case this.STATUS_VALID: this.results[field][validatorName] = this.STATUS_VALID; // Hide error element $errors.filter('[data-bs-validator="' + validatorName + '"]').hide(); // If the field is valid if ($errors.filter(function() { var display = $(this).css('display'), v = $(this).attr('data-bs-validator'); return ('block' == display) || (that.results[field][v] != that.STATUS_VALID); }).length == 0 ) { this._disableSubmitButtons(false); $parent.removeClass('has-error').addClass('has-success'); // Show valid icon if (this.options.feedbackIcons) { $message.find('.form-control-feedback').removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).addClass(this.options.feedbackIcons.valid).show(); } } break; default: break; } return this; }, /** * Mark a field as not validated yet * The plugin doesn't re-validate a field if it is marked as valid. * In some cases, we need to force the plugin validate it again * * @param {String} field The field name * @returns {BootstrapValidator} */ setNotValidated: function(field) { for (var v in this.options.fields[field].validators) { this.results[field][v] = this.STATUS_NOT_VALIDATED; } return this; }, // Useful APIs which aren't used internally /** * Reset the form * * @param {Boolean} resetFormData Reset current form data * @return {BootstrapValidator} */ resetForm: function(resetFormData) { for (var field in this.options.fields) { this.dfds[field] = {}; this.results[field] = {}; // Mark field as not validated yet this.setNotValidated(field); } this.invalidField = null; this.$submitButton = null; // Hide all error elements this.$form .find('.has-error').removeClass('has-error').end() .find('.has-success').removeClass('has-success').end() .find('.help-block[data-bs-validator]').hide(); // Enable submit buttons this._disableSubmitButtons(false); // Hide all feedback icons if (this.options.feedbackIcons) { this.$form.find('.form-control-feedback').removeClass(this.options.feedbackIcons.valid).removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).hide(); } if (resetFormData) { this.$form.get(0).reset(); } return this; }, /** * Enable/Disable all validators to given field * * @param {String} field The field name * @param {Boolean} enabled Enable/Disable field validators * @return {BootstrapValidator} */ enableFieldValidators: function(field, enabled) { this.options.fields[field]['enabled'] = enabled; if (enabled) { this.setNotValidated(field); } else { var $field = this.getFieldElements(field), $message = $field.data('bootstrapValidator.messageContainer'); $field.parents('.form-group').removeClass('has-success has-error'); $message.find('.help-block[data-bs-validator]').hide(); if (this.options.feedbackIcons) { $message.find('.form-control-feedback').removeClass(this.options.feedbackIcons.invalid).removeClass(this.options.feedbackIcons.validating).removeClass(this.options.feedbackIcons.valid).hide(); } this._disableSubmitButtons(false); } return this; } }; // Plugin definition $.fn.bootstrapValidator = function(options) { return this.each(function() { var $this = $(this), data = $this.data('bootstrapValidator'); if (!data) { $this.data('bootstrapValidator', (data = new BootstrapValidator(this, options))); } if ('string' == typeof options) { data[options](); } }); }; // Available validators $.fn.bootstrapValidator.validators = {}; $.fn.bootstrapValidator.Constructor = BootstrapValidator; }(window.jQuery));