ソースを参照

#125: Support dynamic fields

phuoc 11 年 前
コミット
4ff8c8c27e
6 ファイル変更453 行追加209 行削除
  1. 1 0
      CHANGELOG.md
  2. 0 1
      demo/dynamic2.html
  3. 198 0
      demo/dynamic3.html
  4. 126 103
      dist/js/bootstrapValidator.js
  5. 2 2
      dist/js/bootstrapValidator.min.js
  6. 126 103
      src/js/bootstrapValidator.js

+ 1 - 0
CHANGELOG.md

@@ -7,6 +7,7 @@
 * Add ```status.field.bv``` event which is triggered after updating the field status. It can be used to solve [#300](https://github.com/nghuuphuoc/bootstrapvalidator/issues/300), [#301](https://github.com/nghuuphuoc/bootstrapvalidator/issues/301)
 * [#164](https://github.com/nghuuphuoc/bootstrapvalidator/issues/164): Add ```container``` option for indicating the element showing all errors
 * [#175](https://github.com/nghuuphuoc/bootstrapvalidator/issues/175): Showing errors in tooltip or popover
+* [#125](https://github.com/nghuuphuoc/bootstrapvalidator/issues/125): Support dynamic fields
 * [#130](https://github.com/nghuuphuoc/bootstrapvalidator/pull/130): Add ```addField()``` and ```removeField()``` methods for managing dynamic fields, thanks to [@jcnmulio](https://github.com/jcnmulio)
 * [#211](https://github.com/nghuuphuoc/bootstrapvalidator/issues/211), [#235](https://github.com/nghuuphuoc/bootstrapvalidator/issues/235): Add new method ```getInvalidFields()``` that returns all invalid fields
 * [#275](https://github.com/nghuuphuoc/bootstrapvalidator/issues/275): Add ```destroy()``` method

+ 0 - 1
demo/dynamic2.html

@@ -61,7 +61,6 @@
     $(document).ready(function() {
         $('.addPhoneButton').on('click', function() {
             var $that     = $(this),
-                $parent   = $that.parents('.form-group'),
                 $template = $('#template'),
                 $newRow   = $template.clone().removeAttr('id').insertBefore($template).show();
 

+ 198 - 0
demo/dynamic3.html

@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>BootstrapValidator demo</title>
+
+    <link rel="stylesheet" href="../vendor/bootstrap/css/bootstrap.css"/>
+    <link rel="stylesheet" href="../dist/css/bootstrapValidator.css"/>
+
+    <script type="text/javascript" src="../vendor/jquery/jquery-1.10.2.min.js"></script>
+    <script type="text/javascript" src="../vendor/bootstrap/js/bootstrap.min.js"></script>
+    <script type="text/javascript" src="../dist/js/bootstrapValidator.js"></script>
+</head>
+<body>
+<div class="container">
+    <div class="row">
+        <div class="col-lg-8 col-lg-offset-2">
+            <div class="page-header">
+                <h2>Dynamic fields</h2>
+            </div>
+
+            <p>Please enter the information of sender and receiver:</p>
+
+            <form id="shippingForm" method="post" class="form-horizontal" action="target.php">
+                <fieldset>
+                    <legend>Sender</legend>
+
+                    <div class="form-group">
+                        <label class="col-lg-3 control-label">Name</label>
+                        <div class="col-lg-5">
+                            <input class="form-control" type="text" name="senderName" />
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-lg-3 control-label">Phone</label>
+                        <div class="col-lg-5">
+                            <input class="form-control" type="text" name="senderPhone" />
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-lg-3 control-label">Address</label>
+                        <div class="col-lg-5">
+                            <input class="form-control" type="text" name="senderAddress" />
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-lg-3 control-label">City</label>
+                        <div class="col-lg-5">
+                            <input class="form-control" type="text" name="senderCity" />
+                        </div>
+                    </div>
+                </fieldset>
+
+                <fieldset>
+                    <legend>Receiver</legend>
+                    <div class="form-group">
+                        <div class="col-lg-5 col-lg-offset-3">
+                            <div class="checkbox">
+                                <label>
+                                    <input type="checkbox" name="receiver" value="0" checked /> Same as sender
+                                </label>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div id="receiverInfo" style="display: none;">
+                        <div class="form-group">
+                            <label class="col-lg-3 control-label">Name</label>
+                            <div class="col-lg-5">
+                                <input class="form-control" type="text" name="receiverName" />
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-lg-3 control-label">Phone</label>
+                            <div class="col-lg-5">
+                                <input class="form-control" type="text" name="receiverPhone" />
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-lg-3 control-label">Address</label>
+                            <div class="col-lg-5">
+                                <input class="form-control" type="text" name="receiverAddress"
+                                        required data-bv-notempty-message="The address is required" />
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-lg-3 control-label">City</label>
+                            <div class="col-lg-5">
+                                <input class="form-control" type="text" name="receiverCity"
+                                        required data-bv-notempty-message="The city is required" />
+                            </div>
+                        </div>
+                    </div>
+                </fieldset>
+
+                <div class="form-group">
+                    <div class="col-lg-offset-3 col-lg-3">
+                        <button type="submit" class="btn btn-primary">Submit</button>
+                    </div>
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+
+<script type="text/javascript">
+    $(document).ready(function() {
+        $('#shippingForm')
+            .bootstrapValidator({
+                message: 'This value is not valid',
+                feedbackIcons: {
+                    valid: 'glyphicon glyphicon-ok',
+                    invalid: 'glyphicon glyphicon-remove',
+                    validating: 'glyphicon glyphicon-refresh'
+                },
+                fields: {
+                    senderName: {
+                        validators: {
+                            notEmpty: {
+                                message: 'The name is required'
+                            }
+                        }
+                    },
+                    senderPhone: {
+                        message: 'The phone number is not valid',
+                        validators: {
+                            notEmpty: {
+                                message: 'The phone number is required'
+                            },
+                            digits: {
+                                message: 'The value can contain only digits'
+                            }
+                        }
+                    },
+                    senderAddress: {
+                        validators: {
+                            notEmpty: {
+                                message: 'The address is required'
+                            }
+                        }
+                    },
+                    senderCity: {
+                        validators: {
+                            notEmpty: {
+                                message: 'The city is required'
+                            }
+                        }
+                    }
+                }
+            })
+            .find('input[type="checkbox"][name="receiver"]')
+                .on('change', function() {
+                    var sameAsSender   = $(this).is(':checked'),
+                        $receiverPhone = $('#shippingForm').find('input[name="receiverPhone"]').eq(0),
+                        $receiverCity  = $('#shippingForm').find('input[name="receiverCity"]').eq(0);
+
+                    if (sameAsSender) {
+                        $('#receiverInfo').hide();
+
+                        $('#shippingForm')
+                            // Use removeField() method
+                            .bootstrapValidator('removeField', 'receiverName')
+                            .bootstrapValidator('removeField', 'receiverAddress')
+                            // Use removeFieldElement() method
+                            .bootstrapValidator('removeFieldElement', $receiverPhone)
+                            .bootstrapValidator('removeFieldElement', $receiverCity);
+
+                    } else {
+                        $('#receiverInfo').show();
+
+                        $('#shippingForm')
+                            // Use addField() method
+                            .bootstrapValidator('addField', 'receiverName', {
+                                validators: {
+                                    notEmpty: {
+                                        message: 'The name is required'
+                                    }
+                                }
+                            })
+                            .bootstrapValidator('addField', 'receiverAddress')      // The options are automatically parsed from HTML attributes
+                            // Use addFieldElement() method
+                            .bootstrapValidator('addFieldElement', $receiverPhone, {
+                                message: 'The phone number is not valid',
+                                validators: {
+                                    notEmpty: {
+                                        message: 'The phone number is required'
+                                    },
+                                    digits: {
+                                        message: 'The value can contain only digits'
+                                    }
+                                }
+                            })
+                            .bootstrapValidator('addFieldElement', $receiverCity);  // The options are automatically parsed from HTML attributes
+                    }
+                });
+    });
+</script>
+</body>
+</html>

+ 126 - 103
dist/js/bootstrapValidator.js

@@ -162,14 +162,7 @@
                         invalid:    this.$form.attr('data-bv-feedbackicons-invalid'),
                         validating: this.$form.attr('data-bv-feedbackicons-validating')
                     }
-                },
-                validator,
-                v,          // Validator name
-                enabled,
-                optionName,
-                optionValue,
-                html5AttrName,
-                html5Attrs;
+                };
 
             this.$form
                 // Disable client side validation in HTML 5
@@ -193,48 +186,8 @@
                             return;
                         }
 
-                        var field      = $field.attr('name') || $field.attr('data-bv-field'),
-                            validators = {};
-                        for (v in $.fn.bootstrapValidator.validators) {
-                            validator  = $.fn.bootstrapValidator.validators[v];
-                            enabled    = $field.attr('data-bv-' + v.toLowerCase()) + '';
-                            html5Attrs = ('function' == typeof validator.enableByHtml5) ? validator.enableByHtml5($(this)) : null;
-
-                            if ((html5Attrs && enabled != 'false')
-                                || (html5Attrs !== true && ('' == enabled || 'true' == enabled)))
-                            {
-                                // Try to parse the options via attributes
-                                validator.html5Attributes = validator.html5Attributes || { message: 'message' };
-                                validators[v] = $.extend({}, html5Attrs == true ? {} : html5Attrs, validators[v]);
-
-                                for (html5AttrName in validator.html5Attributes) {
-                                    optionName  = validator.html5Attributes[html5AttrName];
-                                    optionValue = $field.attr('data-bv-' + v.toLowerCase() + '-' + html5AttrName);
-                                    if (optionValue) {
-                                        if ('true' == optionValue) {
-                                            optionValue = true;
-                                        } else if ('false' == optionValue) {
-                                            optionValue = false;
-                                        }
-                                        validators[v][optionName] = optionValue;
-                                    }
-                                }
-                            }
-                        }
-
-                        var opts = {
-                                feedbackIcons: $field.attr('data-bv-feedbackicons'),
-                                trigger:       $field.attr('data-bv-trigger'),
-                                message:       $field.attr('data-bv-message'),
-                                container:     $field.attr('data-bv-container'),
-                                selector:      $field.attr('data-bv-selector'),
-                                threshold:     $field.attr('data-bv-threshold')
-                            },
-                            emptyOptions    = $.isEmptyObject(opts),        // Check if the field options are set using HTML attributes
-                            emptyValidators = $.isEmptyObject(validators);  // Check if the field validators are set using HTML attributes
-
-                        if (!emptyValidators || (!emptyOptions && that.options.fields[field])) {
-                            opts.validators = validators;
+                        var opts = that._parseOptions($field);
+                        if (opts) {
                             $field.attr('data-bv-field', field);
                             options.fields[field] = $.extend({}, opts, options.fields[field]);
                         }
@@ -247,6 +200,70 @@
         },
 
         /**
+         * Parse the validator options from HTML attributes
+         *
+         * @param {jQuery} $field The field element
+         * @returns {Object}
+         */
+        _parseOptions: function($field) {
+            var field      = $field.attr('name') || $field.attr('data-bv-field'),
+                validators = {},
+                validator,
+                v,          // Validator name
+                enabled,
+                optionName,
+                optionValue,
+                html5AttrName,
+                html5AttrMap;
+
+            for (v in $.fn.bootstrapValidator.validators) {
+                validator    = $.fn.bootstrapValidator.validators[v];
+                enabled      = $field.attr('data-bv-' + v.toLowerCase()) + '';
+                html5AttrMap = ('function' == typeof validator.enableByHtml5) ? validator.enableByHtml5($field) : null;
+
+                if ((html5AttrMap && enabled != 'false')
+                    || (html5AttrMap !== true && ('' == enabled || 'true' == enabled)))
+                {
+                    // Try to parse the options via attributes
+                    validator.html5Attributes = validator.html5Attributes || { message: 'message' };
+                    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);
+                        if (optionValue) {
+                            if ('true' == optionValue) {
+                                optionValue = true;
+                            } else if ('false' == optionValue) {
+                                optionValue = false;
+                            }
+                            validators[v][optionName] = optionValue;
+                        }
+                    }
+                }
+            }
+
+            var opts = {
+                    feedbackIcons: $field.attr('data-bv-feedbackicons'),
+                    trigger:       $field.attr('data-bv-trigger'),
+                    message:       $field.attr('data-bv-message'),
+                    container:     $field.attr('data-bv-container'),
+                    selector:      $field.attr('data-bv-selector'),
+                    threshold:     $field.attr('data-bv-threshold'),
+                    validators:    validators
+                },
+                emptyOptions    = $.isEmptyObject(opts),        // Check if the field options are set using HTML attributes
+                emptyValidators = $.isEmptyObject(validators);  // Check if the field validators are set using HTML attributes
+
+            if (!emptyValidators || (!emptyOptions && this.options.fields[field])) {
+                opts.validators = validators;
+                return opts;
+            } else {
+                return null;
+            }
+        },
+
+        /**
          * Init field
          *
          * @param {String} field The field name
@@ -564,7 +581,7 @@
          */
         getFieldElements: function(field) {
             if (!this._cacheFields[field]) {
-                this._cacheFields[field] = this.options.fields[field].selector
+                this._cacheFields[field] = (this.options.fields[field] && this.options.fields[field].selector)
                                          ? $(this.options.fields[field].selector)
                                          : this.$form.find('[name="' + field + '"]');
             }
@@ -643,7 +660,7 @@
                 validatorName,
                 validateResult;
 
-            if (!this.options.fields[field]['enabled'] || this._isExcluded($field)) {
+            if (this.options.fields[field]['enabled'] == false || this._isExcluded($field)) {
                 return this;
             }
 
@@ -974,28 +991,65 @@
         },
 
         /**
+         * Add a new field
+         *
+         * @param {String} field The field name
+         * @param {Object} [options] The validator rules
+         * @returns {BootstrapValidator}
+         */
+        addField: function(field, options) {
+            this.options.fields[field] = options;
+            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.addFieldElement($(fields[i]), options);
+            }
+
+            this.disableSubmitButtons(false);
+            return this;
+        },
+
+        /**
+         * Remove a given field
+         *
+         * @param {String} field The field name
+         * @returns {BootstrapValidator}
+         */
+        removeField: function(field) {
+            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.removeFieldElement($(fields[i]));
+            }
+
+            this.disableSubmitButtons(false);
+            return this;
+        },
+
+        /**
          * Add new field element
          *
          * @param {jQuery} $field The field element
-         * @param {Object} options The field options
+         * @param {Object} [options] The field options
          * @returns {BootstrapValidator}
          */
         addFieldElement: function($field, options) {
-            var field      = $field.attr('name') || $field.attr('data-bv-field'),
-                type       = $field.attr('type'),
-                isNewField = !this._cacheFields[field];
+            var field = $field.attr('name');
 
-            // Update cache
-            if (!isNewField && this._cacheFields[field].index($field) == -1) {
-                this._cacheFields[field] = this._cacheFields[field].add($field);
-            }
+            delete this._cacheFields[field];
 
-            if ('checkbox' == type || 'radio' == type || isNewField) {
-                this._initField(field);
-            } else {
-                this._initFieldElement($field);
-            }
+            // Try to parse the options from HTML attributes
+            var opts = this._parseOptions($field);
+            this.options.fields[field] = (opts == null) ? options : $.extend(true, opts, options);
 
+            // Init the element
+            this._initFieldElement($field);
+
+            this.disableSubmitButtons(false);
             return this;
         },
 
@@ -1007,10 +1061,12 @@
          */
         removeFieldElement: function($field) {
             var field = $field.attr('name') || $field.attr('data-bv-field'),
-                type  = $field.attr('type'),
-                index = this._cacheFields[field].index($field);
+                type  = $field.attr('type');
 
+            // Update the cache
+            var index = this._cacheFields[field].index($field);
             (index == -1) ? (delete this._cacheFields[field]) : this._cacheFields[field].splice(index, 1);
+
             // Remove from the list of invalid fields
             index = this.$invalidFields.index($field);
             if (index != -1) {
@@ -1025,48 +1081,15 @@
                 this._initField(field);
             }
 
-            return this;
-        },
-
-        /**
-         * Add a new field
-         *
-         * @param {String} field The field name
-         * @param {Object} options The validator rules
-         * @returns {BootstrapValidator}
-         */
-        addField: function(field, options) {
-            if (!this.options.fields[field]) {
-                this.options.fields[field] = options;
-                this._initField(field);
-            }
-
-            return this;
-        },
-
-        /**
-         * Remove a given field
-         *
-         * @param {String} field The field name
-         * @returns {BootstrapValidator}
-         */
-        removeField: function(field) {
-            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.removeFieldElement($(fields[i]));
-            }
-
+            this.disableSubmitButtons(false);
             return this;
         },
 
         /**
          * Reset the form
          *
-         * @param {Boolean} resetFormData Reset current form data
-         * @return {BootstrapValidator}
+         * @param {Boolean} [resetFormData] Reset current form data
+         * @returns {BootstrapValidator}
          */
         resetForm: function(resetFormData) {
             var field, fields, total, type, validator;

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


+ 126 - 103
src/js/bootstrapValidator.js

@@ -161,14 +161,7 @@
                         invalid:    this.$form.attr('data-bv-feedbackicons-invalid'),
                         validating: this.$form.attr('data-bv-feedbackicons-validating')
                     }
-                },
-                validator,
-                v,          // Validator name
-                enabled,
-                optionName,
-                optionValue,
-                html5AttrName,
-                html5Attrs;
+                };
 
             this.$form
                 // Disable client side validation in HTML 5
@@ -192,48 +185,8 @@
                             return;
                         }
 
-                        var field      = $field.attr('name') || $field.attr('data-bv-field'),
-                            validators = {};
-                        for (v in $.fn.bootstrapValidator.validators) {
-                            validator  = $.fn.bootstrapValidator.validators[v];
-                            enabled    = $field.attr('data-bv-' + v.toLowerCase()) + '';
-                            html5Attrs = ('function' == typeof validator.enableByHtml5) ? validator.enableByHtml5($(this)) : null;
-
-                            if ((html5Attrs && enabled != 'false')
-                                || (html5Attrs !== true && ('' == enabled || 'true' == enabled)))
-                            {
-                                // Try to parse the options via attributes
-                                validator.html5Attributes = validator.html5Attributes || { message: 'message' };
-                                validators[v] = $.extend({}, html5Attrs == true ? {} : html5Attrs, validators[v]);
-
-                                for (html5AttrName in validator.html5Attributes) {
-                                    optionName  = validator.html5Attributes[html5AttrName];
-                                    optionValue = $field.attr('data-bv-' + v.toLowerCase() + '-' + html5AttrName);
-                                    if (optionValue) {
-                                        if ('true' == optionValue) {
-                                            optionValue = true;
-                                        } else if ('false' == optionValue) {
-                                            optionValue = false;
-                                        }
-                                        validators[v][optionName] = optionValue;
-                                    }
-                                }
-                            }
-                        }
-
-                        var opts = {
-                                feedbackIcons: $field.attr('data-bv-feedbackicons'),
-                                trigger:       $field.attr('data-bv-trigger'),
-                                message:       $field.attr('data-bv-message'),
-                                container:     $field.attr('data-bv-container'),
-                                selector:      $field.attr('data-bv-selector'),
-                                threshold:     $field.attr('data-bv-threshold')
-                            },
-                            emptyOptions    = $.isEmptyObject(opts),        // Check if the field options are set using HTML attributes
-                            emptyValidators = $.isEmptyObject(validators);  // Check if the field validators are set using HTML attributes
-
-                        if (!emptyValidators || (!emptyOptions && that.options.fields[field])) {
-                            opts.validators = validators;
+                        var opts = that._parseOptions($field);
+                        if (opts) {
                             $field.attr('data-bv-field', field);
                             options.fields[field] = $.extend({}, opts, options.fields[field]);
                         }
@@ -246,6 +199,70 @@
         },
 
         /**
+         * Parse the validator options from HTML attributes
+         *
+         * @param {jQuery} $field The field element
+         * @returns {Object}
+         */
+        _parseOptions: function($field) {
+            var field      = $field.attr('name') || $field.attr('data-bv-field'),
+                validators = {},
+                validator,
+                v,          // Validator name
+                enabled,
+                optionName,
+                optionValue,
+                html5AttrName,
+                html5AttrMap;
+
+            for (v in $.fn.bootstrapValidator.validators) {
+                validator    = $.fn.bootstrapValidator.validators[v];
+                enabled      = $field.attr('data-bv-' + v.toLowerCase()) + '';
+                html5AttrMap = ('function' == typeof validator.enableByHtml5) ? validator.enableByHtml5($field) : null;
+
+                if ((html5AttrMap && enabled != 'false')
+                    || (html5AttrMap !== true && ('' == enabled || 'true' == enabled)))
+                {
+                    // Try to parse the options via attributes
+                    validator.html5Attributes = validator.html5Attributes || { message: 'message' };
+                    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);
+                        if (optionValue) {
+                            if ('true' == optionValue) {
+                                optionValue = true;
+                            } else if ('false' == optionValue) {
+                                optionValue = false;
+                            }
+                            validators[v][optionName] = optionValue;
+                        }
+                    }
+                }
+            }
+
+            var opts = {
+                    feedbackIcons: $field.attr('data-bv-feedbackicons'),
+                    trigger:       $field.attr('data-bv-trigger'),
+                    message:       $field.attr('data-bv-message'),
+                    container:     $field.attr('data-bv-container'),
+                    selector:      $field.attr('data-bv-selector'),
+                    threshold:     $field.attr('data-bv-threshold'),
+                    validators:    validators
+                },
+                emptyOptions    = $.isEmptyObject(opts),        // Check if the field options are set using HTML attributes
+                emptyValidators = $.isEmptyObject(validators);  // Check if the field validators are set using HTML attributes
+
+            if (!emptyValidators || (!emptyOptions && this.options.fields[field])) {
+                opts.validators = validators;
+                return opts;
+            } else {
+                return null;
+            }
+        },
+
+        /**
          * Init field
          *
          * @param {String} field The field name
@@ -563,7 +580,7 @@
          */
         getFieldElements: function(field) {
             if (!this._cacheFields[field]) {
-                this._cacheFields[field] = this.options.fields[field].selector
+                this._cacheFields[field] = (this.options.fields[field] && this.options.fields[field].selector)
                                          ? $(this.options.fields[field].selector)
                                          : this.$form.find('[name="' + field + '"]');
             }
@@ -642,7 +659,7 @@
                 validatorName,
                 validateResult;
 
-            if (!this.options.fields[field]['enabled'] || this._isExcluded($field)) {
+            if (this.options.fields[field]['enabled'] == false || this._isExcluded($field)) {
                 return this;
             }
 
@@ -973,28 +990,65 @@
         },
 
         /**
+         * Add a new field
+         *
+         * @param {String} field The field name
+         * @param {Object} [options] The validator rules
+         * @returns {BootstrapValidator}
+         */
+        addField: function(field, options) {
+            this.options.fields[field] = options;
+            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.addFieldElement($(fields[i]), options);
+            }
+
+            this.disableSubmitButtons(false);
+            return this;
+        },
+
+        /**
+         * Remove a given field
+         *
+         * @param {String} field The field name
+         * @returns {BootstrapValidator}
+         */
+        removeField: function(field) {
+            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.removeFieldElement($(fields[i]));
+            }
+
+            this.disableSubmitButtons(false);
+            return this;
+        },
+
+        /**
          * Add new field element
          *
          * @param {jQuery} $field The field element
-         * @param {Object} options The field options
+         * @param {Object} [options] The field options
          * @returns {BootstrapValidator}
          */
         addFieldElement: function($field, options) {
-            var field      = $field.attr('name') || $field.attr('data-bv-field'),
-                type       = $field.attr('type'),
-                isNewField = !this._cacheFields[field];
+            var field = $field.attr('name');
 
-            // Update cache
-            if (!isNewField && this._cacheFields[field].index($field) == -1) {
-                this._cacheFields[field] = this._cacheFields[field].add($field);
-            }
+            delete this._cacheFields[field];
 
-            if ('checkbox' == type || 'radio' == type || isNewField) {
-                this._initField(field);
-            } else {
-                this._initFieldElement($field);
-            }
+            // Try to parse the options from HTML attributes
+            var opts = this._parseOptions($field);
+            this.options.fields[field] = (opts == null) ? options : $.extend(true, opts, options);
+
+            // Init the element
+            this._initFieldElement($field);
 
+            this.disableSubmitButtons(false);
             return this;
         },
 
@@ -1006,10 +1060,12 @@
          */
         removeFieldElement: function($field) {
             var field = $field.attr('name') || $field.attr('data-bv-field'),
-                type  = $field.attr('type'),
-                index = this._cacheFields[field].index($field);
+                type  = $field.attr('type');
 
+            // Update the cache
+            var index = this._cacheFields[field].index($field);
             (index == -1) ? (delete this._cacheFields[field]) : this._cacheFields[field].splice(index, 1);
+
             // Remove from the list of invalid fields
             index = this.$invalidFields.index($field);
             if (index != -1) {
@@ -1024,48 +1080,15 @@
                 this._initField(field);
             }
 
-            return this;
-        },
-
-        /**
-         * Add a new field
-         *
-         * @param {String} field The field name
-         * @param {Object} options The validator rules
-         * @returns {BootstrapValidator}
-         */
-        addField: function(field, options) {
-            if (!this.options.fields[field]) {
-                this.options.fields[field] = options;
-                this._initField(field);
-            }
-
-            return this;
-        },
-
-        /**
-         * Remove a given field
-         *
-         * @param {String} field The field name
-         * @returns {BootstrapValidator}
-         */
-        removeField: function(field) {
-            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.removeFieldElement($(fields[i]));
-            }
-
+            this.disableSubmitButtons(false);
             return this;
         },
 
         /**
          * Reset the form
          *
-         * @param {Boolean} resetFormData Reset current form data
-         * @return {BootstrapValidator}
+         * @param {Boolean} [resetFormData] Reset current form data
+         * @returns {BootstrapValidator}
          */
         resetForm: function(resetFormData) {
             var field, fields, total, type, validator;