|
|
@@ -3,7 +3,7 @@
|
|
|
*
|
|
|
* A jQuery plugin to validate form fields. Use with Bootstrap 3
|
|
|
*
|
|
|
- * @version v0.4.5-dev
|
|
|
+ * @version v0.5.0-dev
|
|
|
* @author https://twitter.com/nghuuphuoc
|
|
|
* @copyright (c) 2013 - 2014 Nguyen Huu Phuoc
|
|
|
* @license MIT
|
|
|
@@ -40,6 +40,9 @@
|
|
|
// The flag to indicate that the form is ready to submit when a remote/callback validator returns
|
|
|
this._submitIfValid = null;
|
|
|
|
|
|
+ // Field elements
|
|
|
+ this._cacheFields = {};
|
|
|
+
|
|
|
this._init();
|
|
|
};
|
|
|
|
|
|
@@ -251,7 +254,7 @@
|
|
|
var fields = this.getFieldElements(field);
|
|
|
|
|
|
// We don't need to validate non-existing fields
|
|
|
- if (fields == null) {
|
|
|
+ if (fields == []) {
|
|
|
delete this.options.fields[field];
|
|
|
return;
|
|
|
}
|
|
|
@@ -260,66 +263,91 @@
|
|
|
delete this.options.fields[field].validators[validatorName];
|
|
|
}
|
|
|
}
|
|
|
+ if (this.options.fields[field]['enabled'] == null) {
|
|
|
+ this.options.fields[field]['enabled'] = true;
|
|
|
+ }
|
|
|
|
|
|
+ for (var i = 0; i < fields.length; i++) {
|
|
|
+ this._initFieldElement($(fields[i]));
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ _initFieldElement: function($field) {
|
|
|
var that = this,
|
|
|
- type = fields.attr('type'),
|
|
|
- event = ('radio' == type || 'checkbox' == type || 'file' == type || 'SELECT' == fields[0].tagName) ? 'change' : that._changeEvent,
|
|
|
+ field = $field.attr('name') || $field.attr('data-bv-field'),
|
|
|
+ fields = this.getFieldElements(field),
|
|
|
+ index = fields.index($field),
|
|
|
+ type = $field.attr('type'),
|
|
|
total = fields.length,
|
|
|
- updateAll = (total == 1) || ('radio' == type) || ('checkbox' == type);
|
|
|
-
|
|
|
- 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);
|
|
|
-
|
|
|
- // Set the attribute to indicate the fields which are defined by selector
|
|
|
- if (!$field.attr('data-bv-field')) {
|
|
|
- $field.attr('data-bv-field', field);
|
|
|
- }
|
|
|
-
|
|
|
- // Whenever the user change the field value, mark it as not validated yet
|
|
|
- $field.on(event + '.update.bv', function() {
|
|
|
- // Reset the flag
|
|
|
- that._submitIfValid = false;
|
|
|
- updateAll ? that.updateStatus(field, that.STATUS_NOT_VALIDATED, null)
|
|
|
- : that.updateElementStatus($(this), that.STATUS_NOT_VALIDATED, null);
|
|
|
- });
|
|
|
+ updateAll = (total == 1) || ('radio' == type) || ('checkbox' == type),
|
|
|
+ $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);
|
|
|
+
|
|
|
+ // Remove all error messages and feedback icons
|
|
|
+ $message.find('.help-block[data-bv-validator]').remove();
|
|
|
+ $parent.find('i[data-bv-field]').remove();
|
|
|
+
|
|
|
+ // Set the attribute to indicate the fields which are defined by selector
|
|
|
+ if (!$field.attr('data-bv-field')) {
|
|
|
+ $field.attr('data-bv-field', field);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Whenever the user change the field value, mark it as not validated yet
|
|
|
+ var event = ('radio' == type || 'checkbox' == type || 'file' == type || 'SELECT' == $field.get(0).tagName) ? 'change' : this._changeEvent;
|
|
|
+ $field.off(event + '.update.bv').on(event + '.update.bv', function() {
|
|
|
+ // Reset the flag
|
|
|
+ that._submitIfValid = false;
|
|
|
+ that.updateElementStatus($(this), that.STATUS_NOT_VALIDATED);
|
|
|
+ });
|
|
|
|
|
|
- // Create help block elements for showing the error messages
|
|
|
- $field.data('bv.messages', $message);
|
|
|
- for (validatorName in this.options.fields[field].validators) {
|
|
|
- $field.data('bv.result.' + validatorName, this.STATUS_NOT_VALIDATED);
|
|
|
-
|
|
|
- if (!updateAll || i == total - 1) {
|
|
|
- $('<small/>')
|
|
|
- .css('display', 'none')
|
|
|
- .attr('data-bv-validator', validatorName)
|
|
|
- .attr('data-bv-validator-for', field)
|
|
|
- .html(this.options.fields[field].validators[validatorName].message || this.options.fields[field].message || this.options.message)
|
|
|
- .addClass('help-block')
|
|
|
- .appendTo($message);
|
|
|
- }
|
|
|
+ // Create help block elements for showing the error messages
|
|
|
+ $field.data('bv.messages', $message);
|
|
|
+ for (var validatorName in this.options.fields[field].validators) {
|
|
|
+ $field.data('bv.result.' + validatorName, this.STATUS_NOT_VALIDATED);
|
|
|
+
|
|
|
+ if (!updateAll || index == 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);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // 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);
|
|
|
- }
|
|
|
+ // 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 || index == total - 1))
|
|
|
+ {
|
|
|
+ $parent.removeClass('has-success').removeClass('has-error').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;
|
|
|
+ // Set live mode
|
|
|
+ var trigger = this.options.fields[field].trigger || this.options.trigger || event,
|
|
|
+ events = $.map(trigger.split(' '), function(item) {
|
|
|
+ return item + '.live.bv';
|
|
|
+ }).join(' ');
|
|
|
+ switch (this.options.live) {
|
|
|
+ case 'submitted':
|
|
|
+ break;
|
|
|
+ case 'disabled':
|
|
|
+ $field.off(events);
|
|
|
+ break;
|
|
|
+ case 'enabled':
|
|
|
+ default:
|
|
|
+ $field.off(events).on(events, function() {
|
|
|
+ that.validateFieldElement($(this));
|
|
|
+ });
|
|
|
+ break;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -355,32 +383,16 @@
|
|
|
* Called when all validations are completed
|
|
|
*/
|
|
|
_submit: function() {
|
|
|
- if (!this.isValid()) {
|
|
|
- if ('submitted' == this.options.live) {
|
|
|
- this.setLiveMode('enabled');
|
|
|
- }
|
|
|
+ var isValid = this.isValid(),
|
|
|
+ eventType = isValid ? 'success.form.bv' : 'error.form.bv',
|
|
|
+ e = $.Event(eventType);
|
|
|
|
|
|
- // Focus to the first invalid field
|
|
|
- if (this.$invalidField) {
|
|
|
- // Activate the tab containing the invalid field if exists
|
|
|
- var $tab = this.$invalidField.parents('.tab-pane'),
|
|
|
- tabId;
|
|
|
- if ($tab && (tabId = $tab.attr('id'))) {
|
|
|
- $('a[href="#' + tabId + '"][data-toggle="tab"]').trigger('click.bs.tab.data-api');
|
|
|
- }
|
|
|
+ this.$form.trigger(e);
|
|
|
|
|
|
- this.$invalidField.focus();
|
|
|
- }
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Call the custom submission if enabled
|
|
|
- if (this.options.submitHandler && 'function' == typeof this.options.submitHandler) {
|
|
|
- // If you want to submit the form inside your submit handler, please call defaultSubmit() method
|
|
|
- this.options.submitHandler.call(this, this, this.$form, this.$submitButton);
|
|
|
- } else {
|
|
|
- this.disableSubmitButtons(true).defaultSubmit();
|
|
|
+ // Call default handler
|
|
|
+ // Check if whether the submit button is clicked
|
|
|
+ if (this.$submitButton) {
|
|
|
+ isValid ? this._onSuccess(e) : this._onError(e);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -404,22 +416,76 @@
|
|
|
|
|
|
return false;
|
|
|
},
|
|
|
+
|
|
|
+ // --- Events ---
|
|
|
|
|
|
/**
|
|
|
- * Check if the number of characters of field value exceed the threshold or not
|
|
|
+ * The default handler of error.form.bv event.
|
|
|
+ * It will be called when there is a invalid field
|
|
|
*
|
|
|
- * @param {jQuery} $field The field element
|
|
|
- * @returns {Boolean}
|
|
|
+ * @param {jQuery.Event} e The jQuery event object
|
|
|
*/
|
|
|
- _exceedThreshold: function($field) {
|
|
|
- var field = $field.attr('data-bv-field'),
|
|
|
- threshold = this.options.fields[field].threshold || this.options.threshold;
|
|
|
- if (!threshold) {
|
|
|
- return true;
|
|
|
+ _onError: function(e) {
|
|
|
+ if (e.isDefaultPrevented()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ('submitted' == this.options.live) {
|
|
|
+ // Enable live mode
|
|
|
+ this.options.live = 'enabled';
|
|
|
+ var that = this;
|
|
|
+ for (var field in this.options.fields) {
|
|
|
+ (function(f) {
|
|
|
+ var fields = that.getFieldElements(f);
|
|
|
+ if (fields.length) {
|
|
|
+ var type = $(fields[0]).attr('type'),
|
|
|
+ event = ('radio' == type || 'checkbox' == type || 'file' == type || 'SELECT' == $(fields[0]).get(0).tagName) ? 'change' : that._changeEvent,
|
|
|
+ trigger = that.options.fields[field].trigger || that.options.trigger || event,
|
|
|
+ events = $.map(trigger.split(' '), function(item) {
|
|
|
+ return item + '.live.bv';
|
|
|
+ }).join(' ');
|
|
|
+
|
|
|
+ for (var i = 0; i < fields.length; i++) {
|
|
|
+ $(fields[i]).off(events).on(events, function() {
|
|
|
+ that.validateFieldElement($(this));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })(field);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Focus to the first invalid field
|
|
|
+ if (this.$invalidField) {
|
|
|
+ // Activate the tab containing the invalid field if exists
|
|
|
+ var $tab = this.$invalidField.parents('.tab-pane'),
|
|
|
+ tabId;
|
|
|
+ if ($tab && (tabId = $tab.attr('id'))) {
|
|
|
+ $('a[href="#' + tabId + '"][data-toggle="tab"]').trigger('click.bs.tab.data-api');
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$invalidField.focus();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The default handler of success.form.bv event.
|
|
|
+ * It will be called when all the fields are valid
|
|
|
+ *
|
|
|
+ * @param {jQuery.Event} e The jQuery event object
|
|
|
+ */
|
|
|
+ _onSuccess: function(e) {
|
|
|
+ if (e.isDefaultPrevented()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Call the custom submission if enabled
|
|
|
+ if (this.options.submitHandler && 'function' == typeof this.options.submitHandler) {
|
|
|
+ // If you want to submit the form inside your submit handler, please call defaultSubmit() method
|
|
|
+ this.options.submitHandler.call(this, this, this.$form, this.$submitButton);
|
|
|
+ } else {
|
|
|
+ this.disableSubmitButtons(true).defaultSubmit();
|
|
|
}
|
|
|
- var type = $field.attr('type'),
|
|
|
- cannotType = ['button', 'checkbox', 'file', 'hidden', 'image', 'radio', 'reset', 'submit'].indexOf(type) != -1;
|
|
|
- return (cannotType || $field.val().length >= threshold);
|
|
|
},
|
|
|
|
|
|
// --- Public methods ---
|
|
|
@@ -431,51 +497,13 @@
|
|
|
* @returns {null|jQuery[]}
|
|
|
*/
|
|
|
getFieldElements: function(field) {
|
|
|
- var fields = this.options.fields[field].selector ? $(this.options.fields[field].selector) : this.$form.find('[name="' + field + '"]');
|
|
|
- return (fields.length == 0) ? null : fields;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Set live validating mode
|
|
|
- *
|
|
|
- * @param {String} mode Live validating mode. Can be 'enabled', 'disabled', 'submitted'
|
|
|
- * @returns {BootstrapValidator}
|
|
|
- */
|
|
|
- setLiveMode: function(mode) {
|
|
|
- this.options.live = mode;
|
|
|
- if ('submitted' == mode) {
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- var that = this;
|
|
|
- for (var field in this.options.fields) {
|
|
|
- (function(f) {
|
|
|
- var fields = that.getFieldElements(f);
|
|
|
- if (fields) {
|
|
|
- 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' : that._changeEvent),
|
|
|
- events = $.map(trigger.split(' '), function(item) {
|
|
|
- return item + '.live.bv';
|
|
|
- }).join(' ');
|
|
|
-
|
|
|
- for (var i = 0; i < total; i++) {
|
|
|
- ('enabled' == mode)
|
|
|
- ? $(fields[i]).on(events, function() {
|
|
|
- if (that._exceedThreshold($(this))) {
|
|
|
- updateAll ? that.validateField(f) : that.validateFieldElement($(this), false);
|
|
|
- }
|
|
|
- })
|
|
|
- : $(fields[i]).off(events);
|
|
|
- }
|
|
|
- }
|
|
|
- })(field);
|
|
|
+ if (!this._cacheFields[field]) {
|
|
|
+ this._cacheFields[field] = this.options.fields[field].selector
|
|
|
+ ? $(this.options.fields[field].selector)
|
|
|
+ : this.$form.find('[name="' + field + '"]');
|
|
|
}
|
|
|
|
|
|
- return this;
|
|
|
+ return this._cacheFields[field];
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
@@ -510,10 +538,7 @@
|
|
|
this.validateField(field);
|
|
|
}
|
|
|
|
|
|
- // Check if whether the submit button is clicked
|
|
|
- if (this.$submitButton) {
|
|
|
- this._submit();
|
|
|
- }
|
|
|
+ this._submit();
|
|
|
|
|
|
return this;
|
|
|
},
|
|
|
@@ -530,7 +555,7 @@
|
|
|
n = (('radio' == type) || ('checkbox' == type)) ? 1 : fields.length;
|
|
|
|
|
|
for (var i = 0; i < n; i++) {
|
|
|
- this.validateFieldElement($(fields[i]), (n == 1));
|
|
|
+ this.validateFieldElement($(fields[i]));
|
|
|
}
|
|
|
|
|
|
return this;
|
|
|
@@ -540,12 +565,14 @@
|
|
|
* 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) {
|
|
|
+ validateFieldElement: function($field) {
|
|
|
var that = this,
|
|
|
field = $field.attr('data-bv-field'),
|
|
|
+ fields = this.getFieldElements(field),
|
|
|
+ type = $field.attr('type'),
|
|
|
+ updateAll = (fields && fields.length == 1) || ('radio' == type) || ('checkbox' == type),
|
|
|
validators = this.options.fields[field].validators,
|
|
|
validatorName,
|
|
|
validateResult;
|
|
|
@@ -776,6 +803,52 @@
|
|
|
// Useful APIs which aren't used internally
|
|
|
|
|
|
/**
|
|
|
+ * Add new field element
|
|
|
+ *
|
|
|
+ * @param {jQuery} $field The field element
|
|
|
+ * @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];
|
|
|
+
|
|
|
+ // Update cache
|
|
|
+ if (!isNewField && this._cacheFields[field].index($field) == -1) {
|
|
|
+ this._cacheFields[field] = this._cacheFields[field].add($field);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ('checkbox' == type || 'radio' == type || isNewField) {
|
|
|
+ this._initField(field);
|
|
|
+ } else {
|
|
|
+ this._initFieldElement($field);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Remove given field element
|
|
|
+ *
|
|
|
+ * @param {jQuery} $field The field element
|
|
|
+ * @returns {BootstrapValidator}
|
|
|
+ */
|
|
|
+ removeFieldElement: function($field) {
|
|
|
+ var field = $field.attr('name') || $field.attr('data-bv-field'),
|
|
|
+ type = $field.attr('type'),
|
|
|
+ index = this._cacheFields[field].index($field);
|
|
|
+
|
|
|
+ (index == -1) ? (delete this._cacheFields[field]) : this._cacheFields[field].splice(index, 1);
|
|
|
+
|
|
|
+ if ('checkbox' == type || 'radio' == type) {
|
|
|
+ this._initField(field);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
* Reset the form
|
|
|
*
|
|
|
* @param {Boolean} resetFormData Reset current form data
|
|
|
@@ -794,7 +867,7 @@
|
|
|
}
|
|
|
|
|
|
// Mark field as not validated yet
|
|
|
- this.updateStatus(field, this.STATUS_NOT_VALIDATED, null);
|
|
|
+ this.updateStatus(field, this.STATUS_NOT_VALIDATED);
|
|
|
|
|
|
if (resetFormData) {
|
|
|
type = fields.attr('type');
|
|
|
@@ -820,7 +893,7 @@
|
|
|
*/
|
|
|
enableFieldValidators: function(field, enabled) {
|
|
|
this.options.fields[field]['enabled'] = enabled;
|
|
|
- this.updateStatus(field, this.STATUS_NOT_VALIDATED, null);
|
|
|
+ this.updateStatus(field, this.STATUS_NOT_VALIDATED);
|
|
|
|
|
|
return this;
|
|
|
}
|