ソースを参照

#136: Support multiple elements with the same name

phuoc 11 年 前
コミット
118178743b
3 ファイル変更279 行追加167 行削除
  1. 180 109
      dist/js/bootstrapValidator.js
  2. 1 1
      dist/js/bootstrapValidator.min.js
  3. 98 57
      src/js/bootstrapValidator.js

+ 180 - 109
dist/js/bootstrapValidator.js

@@ -16,15 +16,16 @@
 
         this.results = {};          // Validating results
 
-        this.invalidField  = null;  // First invalid field
+        this.$invalidField = null;  // First invalid field
         this.$submitButton = null;  // The submit button which is clicked to submit form
 
-        this._init();
-
+        // Validating status
         this.STATUS_NOT_VALIDATED = 'NOT_VALIDATED';
         this.STATUS_VALIDATING    = 'VALIDATING';
         this.STATUS_INVALID       = 'INVALID';
         this.STATUS_VALID         = 'VALID';
+
+        this._init();
     };
 
     // The default options
@@ -116,7 +117,7 @@
                 .attr('novalidate', 'novalidate')
                 .addClass(this.options.elementClass)
                 // Disable the default submission first
-                .on('submit.bootstrapValidator', function(e) {
+                .on('submit.bv', function(e) {
                     e.preventDefault();
                     that.validate();
                 })
@@ -197,59 +198,70 @@
                 delete this.options.fields[field];
                 return;
             }
+            for (var validatorName in this.options.fields[field].validators) {
+                if (!$.fn.bootstrapValidator.validators[validatorName]) {
+                    delete this.options.fields[field].validators[validatorName];
+                }
+            }
 
             // Set the attribute to indicate the fields which are defined by selector
-            if (fields.attr('data-bv-field')) {
+            if (!fields.attr('data-bv-field')) {
                 fields.attr('data-bv-field', field);
             }
 
-            // Create help block elements for showing the error messages
-            var $field   = $(fields[0]),
-                $parent  = $field.parents('.form-group'),
-                // Allow user to indicate where the error messages are shown
-                $message = this.options.fields[field].container ? $parent.find(this.options.fields[field].container) : this._getMessageContainer($field);
+            var that      = this,
+                type      = fields.attr('type'),
+                event     = ('radio' == type || 'checkbox' == type || 'file' == type || 'SELECT' == fields[0].tagName) ? 'change' : 'keyup',
+                total     = fields.length,
+                updateAll = (total == 1) || ('radio' == type) || ('checkbox' == type);
 
-            $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;
+            for (var i = 0; i < total; i++) {
+                var $field   = $(fields[i]),
+                    $parent  = $field.parents('.form-group'),
+                    // Allow user to indicate where the error messages are shown
+                    $message = this.options.fields[field].container ? $parent.find(this.options.fields[field].container) : this._getMessageContainer($field);
+
+                // Whenever the user change the field value, mark it as not validated yet
+                $field.on(event + '.bv', function() {
+                    updateAll ? that.updateStatus(field, that.STATUS_NOT_VALIDATED, null)
+                              : that.updateElementStatus($(this), that.STATUS_NOT_VALIDATED, null);
+                });
+
+                // Create help block elements for showing the error messages
+                $field.data('bv.messageContainer', $message);
+                for (validatorName in this.options.fields[field].validators) {
+                    $field.data('bv.result.' + validatorName, this.STATUS_NOT_VALIDATED);
+                    //this.results[field][validatorName] = this.STATUS_NOT_VALIDATED;
+
+                    if (!updateAll || i == total - 1) {
+                        $('<small/>')
+                            .css('display', 'none')
+                            .attr('data-bv-validator', validatorName)
+                            .html(this.options.fields[field].validators[validatorName].message || this.options.fields[field].message || this.options.message)
+                            .addClass('help-block')
+                            .appendTo($message);
+                    }
                 }
 
-                this.results[field][validatorName] = this.STATUS_NOT_VALIDATED;
-                $('<small/>')
-                    .css('display', 'none')
-                    .attr('data-bv-validator', validatorName)
-                    .html(this.options.fields[field].validators[validatorName].message || this.options.fields[field].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
-                && this.options.feedbackIcons.validating && this.options.feedbackIcons.invalid && this.options.feedbackIcons.valid)
-            {
-                $parent.addClass('has-feedback');
-                var $icon = $('<i/>').css('display', 'none').addClass('form-control-feedback').attr('data-bv-field', field).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);
+                // Prepare the feedback icons
+                // Available from Bootstrap 3.1 (http://getbootstrap.com/css/#forms-control-validation)
+                if (this.options.feedbackIcons
+                    && this.options.feedbackIcons.validating && this.options.feedbackIcons.invalid && this.options.feedbackIcons.valid
+                    && (!updateAll || i == total - 1))
+                {
+                    $parent.addClass('has-feedback');
+                    var $icon = $('<i/>').css('display', 'none').addClass('form-control-feedback').attr('data-bv-field', field).insertAfter($field);
+                    // 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 || 'file' == type || 'SELECT' == fields[0].tagName) ? 'change' : 'keyup';
-            fields.on(event + '.bootstrapValidator', function() {
-                that.updateStatus($field, that.STATUS_NOT_VALIDATED, null);
-            });
         },
 
         /**
@@ -290,17 +302,21 @@
                     (function(f) {
                         var fields = that.getFieldElements(f);
                         if (fields) {
-                            var type    = fields.attr('type'),
-                                trigger = that.options.fields[field].trigger
-                                        || that.options.trigger
-                                        || (('radio' == type || 'checkbox' == type || 'file' == type || 'SELECT' == fields[0].tagName) ? 'change' : 'keyup'),
-                                events  = trigger.split(' ').map(function(item) {
-                                    return item + '.bootstrapValidator';
+                            var type      = fields.attr('type'),
+                                total     = fields.length,
+                                updateAll = (total == 1) || ('radio' == type) || ('checkbox' == type),
+                                trigger   = that.options.fields[field].trigger
+                                            || that.options.trigger
+                                            || (('radio' == type || 'checkbox' == type || 'file' == type || 'SELECT' == fields[0].tagName) ? 'change' : 'keyup'),
+                                events    = trigger.split(' ').map(function(item) {
+                                    return item + '.bv';
                                 }).join(' ');
 
-                            fields.on(events, function() {
-                                that.validateField(f);
-                            });
+                            for (var i = 0; i < total; i++) {
+                                $(fields[i]).on(events, function () {
+                                    updateAll ? that.validateField(f) : that.validateFieldElement($(this), false);
+                                });
+                            }
                         }
                     })(field);
                 }
@@ -332,8 +348,8 @@
                 }
 
                 // Focus to the first invalid field
-                if (this.invalidField) {
-                    this.getFieldElements(this.invalidField).focus();
+                if (this.$invalidField) {
+                    this.$invalidField.focus();
                 }
                 return;
             }
@@ -343,11 +359,11 @@
             // Call the custom submission if enabled
             if (this.options.submitHandler && 'function' == typeof this.options.submitHandler) {
                 // Turn off the submit handler, so user can call form.submit() inside their submitHandler method
-                this.$form.off('submit.bootstrapValidator');
+                this.$form.off('submit.bv');
                 this.options.submitHandler.call(this, this, this.$form, this.$submitButton);
             } else {
                 // Submit form
-                this.$form.off('submit.bootstrapValidator').submit();
+                this.$form.off('submit.bv').submit();
             }
         },
 
@@ -386,24 +402,38 @@
         /**
          * Validate given field
          *
-         * @param {String} field The field name
+         * @param {String} field The field element
+         * @returns {BootstrapValidator}
          */
         validateField: function(field) {
-            if (!this.options.fields[field]['enabled']) {
-                return;
+            var fields = this.getFieldElements(field),
+                type   = fields.attr('type'),
+                n      = (('radio' == type) || ('checkbox' == type)) ? 1 : fields.length;
+
+            for (var i = 0; i < n; i++) {
+                this.validateFieldElement($(fields[i]), (n == 1));
             }
 
+            return this;
+        },
+
+        /**
+         * Validate field element
+         *
+         * @param {jQuery} $field The field element
+         * @param {Boolean} updateAll If true, update status of all elements which have the same name
+         * @returns {BootstrapValidator}
+         */
+        validateFieldElement: function($field, updateAll) {
             var that       = this,
-                fields     = this.getFieldElements(field),
-                $field     = $(fields[0]),
+                field      = $field.attr('data-bv-field'),
                 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];
-                return;
+            if ($field.is(':disabled')) {
+                return this;
             }
 
             for (validatorName in validators) {
@@ -412,83 +442,82 @@
                 }
 
                 // Don't validate field if it is already done
-                if (this.results[field][validatorName] == this.STATUS_VALID || this.results[field][validatorName] == this.STATUS_INVALID) {
+                var result = $field.data('bv.result.' + validatorName);
+                if (result == this.STATUS_VALID || result == this.STATUS_INVALID) {
                     continue;
                 }
 
-                this.results[field][validatorName] = this.STATUS_VALIDATING;
+                $field.data('bv.result.' + 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);
+                    updateAll ? this.updateStatus(field, this.STATUS_VALIDATING, validatorName)
+                              : this.updateElementStatus($field, this.STATUS_VALIDATING, validatorName);
                     $field.data('bv.dfs.' + validatorName, validateResult);
 
                     validateResult.done(function($f, v, isValid) {
                         // v is validator name
                         $f.removeData('bv.dfs.' + v);
-                        that.updateStatus(field, isValid ? that.STATUS_VALID : that.STATUS_INVALID, v);
+                        updateAll ? that.updateStatus($f.attr('data-bv-field'), isValid ? that.STATUS_VALID : that.STATUS_INVALID, v)
+                                  : that.updateElementStatus($f, 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);
+                    updateAll ? this.updateStatus(field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName)
+                              : this.updateElementStatus($field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
                 }
             }
+
+            return this;
         },
 
         /**
-         * Check the form validity
+         * Update all validating results of elements which have the same field name
          *
-         * @returns {Boolean}
+         * @param {String} field The field name
+         * @param {String} status The status
+         * Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID' or 'VALID'
+         * @param {String|null} validatorName The validator name. If null, the method updates validity result for all validators
+         * @return {BootstrapValidator}
          */
-        isValid: function() {
-            var field, validatorName;
-            for (field in this.results) {
-                if (this.options.fields[field] == null || !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;
-                    }
+        updateStatus: function(field, status, validatorName) {
+            var fields = this.getFieldElements(field),
+                type   = fields.attr('type'),
+                n      = (('radio' == type) || ('checkbox' == type)) ? 1 : fields.length;
 
-                    if (this.results[field][validatorName] == this.STATUS_INVALID) {
-                        this.invalidField = field;
-                        return false;
-                    }
-                }
+            for (var i = 0; i < n; i++) {
+                this.updateElementStatus($(fields[i]), status, validatorName);
             }
 
-            return true;
+            return this;
         },
 
         /**
-         * Update field status
+         * Update validating result of given element
          *
-         * @param {String|jQuery} field The field name or field element
+         * @param {String} field The field name
          * @param {String} status The status
          * Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID' or 'VALID'
          * @param {String|null} validatorName The validator name. If null, the method updates validity result for all validators
          * @return {BootstrapValidator}
          */
-        updateStatus: function(field, status, validatorName) {
-            var $field   = ('string' == typeof field) ? this.getFieldElements(field) : field,
-                that     = this,
+        updateElementStatus: function($field, status, validatorName) {
+            var that     = this,
                 field    = $field.attr('data-bv-field'),
                 $parent  = $field.parents('.form-group'),
-                $message = $field.data('bootstrapValidator.messageContainer'),
+                $message = $field.data('bv.messageContainer'),
                 $errors  = $message.find('.help-block[data-bv-validator]'),
                 $icon    = $parent.find('.form-control-feedback[data-bv-field="' + field + '"]');
 
             // Update status
             if (validatorName) {
-                this.results[field][validatorName] = status;
+                $field.data('bv.result.' + validatorName, status);
             } else {
                 for (var v in this.options.fields[field].validators) {
-                    this.results[field][v] = status;
+                    $field.data('bv.result.' + v, status);
                 }
             }
 
@@ -518,10 +547,10 @@
 
                     // If the field is valid
                     if ($errors.filter(function() {
-                            var display = $(this).css('display'), v = $(this).attr('data-bv-validator');
-                            return ('block' == display) || (that.results[field][v] != that.STATUS_VALID);
-                        }).length == 0
-                    ) {
+                        var display = $(this).css('display'), v = $(this).attr('data-bv-validator');
+                        return ('block' == display) || ($field.data('bv.result.' + v) != that.STATUS_VALID);
+                    }).length == 0
+                        ) {
                         this._disableSubmitButtons(false);
                         $parent.removeClass('has-error').addClass('has-success');
                         if ($icon) {
@@ -544,6 +573,43 @@
             return this;
         },
 
+        /**
+         * Check the form validity
+         *
+         * @returns {Boolean}
+         */
+        isValid: function() {
+            var fields, field, $field,
+                type, status, validatorName,
+                n, i;
+            for (field in this.options.fields) {
+                if (this.options.fields[field] == null || !this.options.fields[field]['enabled']) {
+                    continue;
+                }
+
+                fields = this.getFieldElements(field);
+                type   = fields.attr('type');
+                n      = (('radio' == type) || ('checkbox' == type)) ? 1 : fields.length;
+
+                for (i = 0; i < n; i++) {
+                    $field = $(fields[i]);
+                    for (validatorName in this.options.fields[field].validators) {
+                        status = $field.data('bv.result.' + validatorName);
+                        if (status == this.STATUS_NOT_VALIDATED || status == this.STATUS_VALIDATING) {
+                            return false;
+                        }
+
+                        if (status == this.STATUS_INVALID) {
+                            this.$invalidField = $field;
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            return true;
+        },
+
         // Useful APIs which aren't used internally
 
         /**
@@ -553,22 +619,27 @@
          * @return {BootstrapValidator}
          */
         resetForm: function(resetFormData) {
-            var field, $field, type;
+            var field, fields, total, type, validator;
             for (field in this.options.fields) {
-                this.results[field] = {};
+                fields = this.getFieldElements(field);
+                total  = fields.length;
+
+                for (var i = 0; i < total; i++) {
+                    for (validator in this.options.fields[field].validators) {
+                        $(fields[i]).removeData('bv.dfs.' + validator);
+                    }
+                }
 
-                $field = this.getFieldElements(field);
-                $field.removeData('bv.dfs');
                 // Mark field as not validated yet
-                this.updateStatus($field, this.STATUS_NOT_VALIDATED, null);
+                this.updateStatus(field, this.STATUS_NOT_VALIDATED, null);
 
                 if (resetFormData) {
-                    type = $field.attr('type');
-                    ('radio' == type || 'checkbox' == type) ? $field.removeAttr('checked').removeAttr('selected') : $field.val('');
+                    type = fields.attr('type');
+                    ('radio' == type || 'checkbox' == type) ? fields.removeAttr('checked').removeAttr('selected') : fields.val('');
                 }
             }
 
-            this.invalidField  = null;
+            this.$invalidField = null;
             this.$submitButton = null;
 
             // Enable submit buttons
@@ -1611,7 +1682,7 @@
                 };
             }
 
-            return true;
+            return maxLength;
         },
 
         /**

ファイルの差分が大きいため隠しています
+ 1 - 1
dist/js/bootstrapValidator.min.js


+ 98 - 57
src/js/bootstrapValidator.js

@@ -15,7 +15,7 @@
 
         this.results = {};          // Validating results
 
-        this.invalidField  = null;  // First invalid field
+        this.$invalidField = null;  // First invalid field
         this.$submitButton = null;  // The submit button which is clicked to submit form
 
         // Validating status
@@ -116,7 +116,7 @@
                 .attr('novalidate', 'novalidate')
                 .addClass(this.options.elementClass)
                 // Disable the default submission first
-                .on('submit.bootstrapValidator', function(e) {
+                .on('submit.bv', function(e) {
                     e.preventDefault();
                     that.validate();
                 })
@@ -212,7 +212,7 @@
                 type      = fields.attr('type'),
                 event     = ('radio' == type || 'checkbox' == type || 'file' == type || 'SELECT' == fields[0].tagName) ? 'change' : 'keyup',
                 total     = fields.length,
-                updateAll = (total == 1) || ('radio' == type || 'checkbox' == type);
+                updateAll = (total == 1) || ('radio' == type) || ('checkbox' == type);
 
             for (var i = 0; i < total; i++) {
                 var $field   = $(fields[i]),
@@ -222,18 +222,19 @@
 
                 // Whenever the user change the field value, mark it as not validated yet
                 $field.on(event + '.bv', function() {
-                    that.updateStatus(updateAll ? field : $(this), that.STATUS_NOT_VALIDATED, null);
+                    updateAll ? that.updateStatus(field, that.STATUS_NOT_VALIDATED, null)
+                              : that.updateElementStatus($(this), that.STATUS_NOT_VALIDATED, null);
                 });
 
                 // Create help block elements for showing the error messages
-                $field.data('bootstrapValidator.messageContainer', $message);
+                $field.data('bv.messageContainer', $message);
                 for (validatorName in this.options.fields[field].validators) {
                     $field.data('bv.result.' + validatorName, this.STATUS_NOT_VALIDATED);
                     //this.results[field][validatorName] = this.STATUS_NOT_VALIDATED;
 
                     if (!updateAll || i == total - 1) {
                         $('<small/>')
-                            //.css('display', 'none')
+                            .css('display', 'none')
                             .attr('data-bv-validator', validatorName)
                             .html(this.options.fields[field].validators[validatorName].message || this.options.fields[field].message || this.options.message)
                             .addClass('help-block')
@@ -312,7 +313,7 @@
 
                             for (var i = 0; i < total; i++) {
                                 $(fields[i]).on(events, function () {
-                                    updateAll ? that.validateField(f) : that.validateFieldElement($(this));
+                                    updateAll ? that.validateField(f) : that.validateFieldElement($(this), false);
                                 });
                             }
                         }
@@ -346,8 +347,8 @@
                 }
 
                 // Focus to the first invalid field
-                if (this.invalidField) {
-                    this.getFieldElements(this.invalidField).focus();
+                if (this.$invalidField) {
+                    this.$invalidField.focus();
                 }
                 return;
             }
@@ -357,11 +358,11 @@
             // Call the custom submission if enabled
             if (this.options.submitHandler && 'function' == typeof this.options.submitHandler) {
                 // Turn off the submit handler, so user can call form.submit() inside their submitHandler method
-                this.$form.off('submit.bootstrapValidator');
+                this.$form.off('submit.bv');
                 this.options.submitHandler.call(this, this, this.$form, this.$submitButton);
             } else {
                 // Submit form
-                this.$form.off('submit.bootstrapValidator').submit();
+                this.$form.off('submit.bv').submit();
             }
         },
 
@@ -409,7 +410,7 @@
                 n      = (('radio' == type) || ('checkbox' == type)) ? 1 : fields.length;
 
             for (var i = 0; i < n; i++) {
-                this.validateFieldElement($(fields[i]));
+                this.validateFieldElement($(fields[i]), (n == 1));
             }
 
             return this;
@@ -419,9 +420,10 @@
          * Validate field element
          *
          * @param {jQuery} $field The field element
+         * @param {Boolean} updateAll If true, update status of all elements which have the same name
          * @returns {BootstrapValidator}
          */
-        validateFieldElement: function($field) {
+        validateFieldElement: function($field, updateAll) {
             var that       = this,
                 field      = $field.attr('data-bv-field'),
                 validators = this.options.fields[field].validators,
@@ -439,28 +441,32 @@
                 }
 
                 // Don't validate field if it is already done
-                if (this.results[field][validatorName] == this.STATUS_VALID || this.results[field][validatorName] == this.STATUS_INVALID) {
+                var result = $field.data('bv.result.' + validatorName);
+                if (result == this.STATUS_VALID || result == this.STATUS_INVALID) {
                     continue;
                 }
 
-                this.results[field][validatorName] = this.STATUS_VALIDATING;
+                $field.data('bv.result.' + 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);
+                    updateAll ? this.updateStatus(field, this.STATUS_VALIDATING, validatorName)
+                              : this.updateElementStatus($field, this.STATUS_VALIDATING, validatorName);
                     $field.data('bv.dfs.' + validatorName, validateResult);
 
                     validateResult.done(function($f, v, isValid) {
                         // v is validator name
                         $f.removeData('bv.dfs.' + v);
-                        that.updateStatus(field, isValid ? that.STATUS_VALID : that.STATUS_INVALID, v);
+                        updateAll ? that.updateStatus($f.attr('data-bv-field'), isValid ? that.STATUS_VALID : that.STATUS_INVALID, v)
+                                  : that.updateElementStatus($f, 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);
+                    updateAll ? this.updateStatus(field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName)
+                              : this.updateElementStatus($field, validateResult ? this.STATUS_VALID : this.STATUS_INVALID, validatorName);
                 }
             }
 
@@ -468,56 +474,49 @@
         },
 
         /**
-         * Check the form validity
+         * Update all validating results of elements which have the same field name
          *
-         * @returns {Boolean}
+         * @param {String} field The field name
+         * @param {String} status The status
+         * Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID' or 'VALID'
+         * @param {String|null} validatorName The validator name. If null, the method updates validity result for all validators
+         * @return {BootstrapValidator}
          */
-        isValid: function() {
-            var field, validatorName;
-            for (field in this.results) {
-                if (this.options.fields[field] == null || !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;
-                    }
+        updateStatus: function(field, status, validatorName) {
+            var fields = this.getFieldElements(field),
+                type   = fields.attr('type'),
+                n      = (('radio' == type) || ('checkbox' == type)) ? 1 : fields.length;
 
-                    if (this.results[field][validatorName] == this.STATUS_INVALID) {
-                        this.invalidField = field;
-                        return false;
-                    }
-                }
+            for (var i = 0; i < n; i++) {
+                this.updateElementStatus($(fields[i]), status, validatorName);
             }
 
-            return true;
+            return this;
         },
 
         /**
-         * Update field status
+         * Update validating result of given element
          *
-         * @param {String|jQuery} field The field name or field element
+         * @param {String} field The field name
          * @param {String} status The status
          * Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID' or 'VALID'
          * @param {String|null} validatorName The validator name. If null, the method updates validity result for all validators
          * @return {BootstrapValidator}
          */
-        updateStatus: function(field, status, validatorName) {
-            var $field   = ('string' == typeof field) ? this.getFieldElements(field) : field,
-                that     = this,
+        updateElementStatus: function($field, status, validatorName) {
+            var that     = this,
                 field    = $field.attr('data-bv-field'),
                 $parent  = $field.parents('.form-group'),
-                $message = $field.data('bootstrapValidator.messageContainer'),
+                $message = $field.data('bv.messageContainer'),
                 $errors  = $message.find('.help-block[data-bv-validator]'),
                 $icon    = $parent.find('.form-control-feedback[data-bv-field="' + field + '"]');
 
             // Update status
             if (validatorName) {
-                this.results[field][validatorName] = status;
+                $field.data('bv.result.' + validatorName, status);
             } else {
                 for (var v in this.options.fields[field].validators) {
-                    this.results[field][v] = status;
+                    $field.data('bv.result.' + v, status);
                 }
             }
 
@@ -547,10 +546,10 @@
 
                     // If the field is valid
                     if ($errors.filter(function() {
-                            var display = $(this).css('display'), v = $(this).attr('data-bv-validator');
-                            return ('block' == display) || (that.results[field][v] != that.STATUS_VALID);
-                        }).length == 0
-                    ) {
+                        var display = $(this).css('display'), v = $(this).attr('data-bv-validator');
+                        return ('block' == display) || ($field.data('bv.result.' + v) != that.STATUS_VALID);
+                    }).length == 0
+                        ) {
                         this._disableSubmitButtons(false);
                         $parent.removeClass('has-error').addClass('has-success');
                         if ($icon) {
@@ -573,6 +572,43 @@
             return this;
         },
 
+        /**
+         * Check the form validity
+         *
+         * @returns {Boolean}
+         */
+        isValid: function() {
+            var fields, field, $field,
+                type, status, validatorName,
+                n, i;
+            for (field in this.options.fields) {
+                if (this.options.fields[field] == null || !this.options.fields[field]['enabled']) {
+                    continue;
+                }
+
+                fields = this.getFieldElements(field);
+                type   = fields.attr('type');
+                n      = (('radio' == type) || ('checkbox' == type)) ? 1 : fields.length;
+
+                for (i = 0; i < n; i++) {
+                    $field = $(fields[i]);
+                    for (validatorName in this.options.fields[field].validators) {
+                        status = $field.data('bv.result.' + validatorName);
+                        if (status == this.STATUS_NOT_VALIDATED || status == this.STATUS_VALIDATING) {
+                            return false;
+                        }
+
+                        if (status == this.STATUS_INVALID) {
+                            this.$invalidField = $field;
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            return true;
+        },
+
         // Useful APIs which aren't used internally
 
         /**
@@ -582,22 +618,27 @@
          * @return {BootstrapValidator}
          */
         resetForm: function(resetFormData) {
-            var field, $field, type;
+            var field, fields, total, type, validator;
             for (field in this.options.fields) {
-                this.results[field] = {};
+                fields = this.getFieldElements(field);
+                total  = fields.length;
+
+                for (var i = 0; i < total; i++) {
+                    for (validator in this.options.fields[field].validators) {
+                        $(fields[i]).removeData('bv.dfs.' + validator);
+                    }
+                }
 
-                $field = this.getFieldElements(field);
-                $field.removeData('bv.dfs');
                 // Mark field as not validated yet
-                this.updateStatus($field, this.STATUS_NOT_VALIDATED, null);
+                this.updateStatus(field, this.STATUS_NOT_VALIDATED, null);
 
                 if (resetFormData) {
-                    type = $field.attr('type');
-                    ('radio' == type || 'checkbox' == type) ? $field.removeAttr('checked').removeAttr('selected') : $field.val('');
+                    type = fields.attr('type');
+                    ('radio' == type || 'checkbox' == type) ? fields.removeAttr('checked').removeAttr('selected') : fields.val('');
                 }
             }
 
-            this.invalidField  = null;
+            this.$invalidField = null;
             this.$submitButton = null;
 
             // Enable submit buttons