diff --git a/jquery.h5validate.js b/jquery.h5validate.js
index b151b06..0efb9be 100644
--- a/jquery.h5validate.js
+++ b/jquery.h5validate.js
@@ -92,6 +92,9 @@
// Callback stubs
invalidCallback: function () {},
validCallback: function () {},
+
+ // Array of Validator Functions. View the comment for addValidator for more information.
+ validators: [],
// Elements to validate with allValid (only validating visible elements)
allValidSelectors: ':input:visible:not(:button):not(:disabled):not(.novalidate)',
@@ -139,7 +142,7 @@
$element.removeClass(options.errorClass).removeClass(options.validClass);
$element.form.find("#" + options.element.id).removeClass(options.errorClass).removeClass(options.validClass);
return $element;
- }
+ },
}
},
@@ -150,17 +153,75 @@
createValidity = function createValidity(validity) {
return $.extend({
customError: validity.customError || false,
- patternMismatch: validity.patternMismatch || false,
+ failedValidatorNames: [],
rangeOverflow: validity.rangeOverflow || false,
rangeUnderflow: validity.rangeUnderflow || false,
stepMismatch: validity.stepMismatch || false,
- tooLong: validity.tooLong || false,
typeMismatch: validity.typeMismatch || false,
valid: validity.valid || true,
- valueMissing: validity.valueMissing || false
}, validity);
},
+ /**
+ * Add builtin validators to h5Validate. Currently, this adds the following validators:
+ * Required
+ * Maxlength
+ * Pattern
+ * @param {object} settings instance settings
+ */
+ addBuiltinValidators = function (settings) {
+ settings.validators.push({selector: "*", validator: function(value) {
+ var maxlength = parseInt($(this).attr('maxlength'), 10);
+ return isNaN(maxlength) || value.length <= maxlength;
+ }, options: {
+ validityFailureFlag: 'tooLong',
+ name: 'maxlength',
+ }});
+
+ settings.validators.push({selector: "*", validator: function(value) {
+ var required = false,
+ $checkRequired = $(''),
+ $this = $(this);
+
+ /* If the required attribute exists, set it required to true, unless it's set 'false'.
+ * This is a minor deviation from the spec, but it seems some browsers have falsey
+ * required values if the attribute is empty (should be true). The more conformant
+ * version of this failed sanity checking in the browser environment.
+ * This plugin is meant to be practical, not ideologically married to the spec.
+ */
+ if ($checkRequired.filter('[required]') && $checkRequired.filter('[required]').length) {
+ required = ($this.filter('[required]').length && $this.attr('required') !== 'false');
+ } else {
+ required = ($this.attr('required') !== undefined);
+ }
+
+ return !required || value;
+ }, options: {
+ validityFailureFlag: 'valueMissing',
+ name: 'required',
+ }});
+
+ settings.validators.push({selector: "*", validator: function(value) {
+ // Get the HTML5 pattern attribute if it exists.
+ // ** TODO: If a pattern class exists, grab the pattern from the patternLibrary, but the pattern attrib should override that value.
+ var $this = $(this),
+ pattern = $this.filter('[pattern]')[0] ? $this.attr('pattern') : false,
+ // The pattern attribute must match the whole value, not just a subset:
+ // "...as if it implied a ^(?: at the start of the pattern and a )$ at the end."
+ re = new RegExp('^(?:' + pattern + ')$');
+
+ if (settings.debug && window.console) {
+ console.log('Validate called on "' + value + '" with regex "' + re + '".'); // **DEBUG
+ console.log('Regex test: ' + re.test(value) + ', Pattern: ' + pattern); // **DEBUG
+ }
+
+ return !pattern || re.test(value) || !value;
+ }, options: {
+ validityFailureFlag: 'patternMismatch',
+ name: 'pattern',
+ }});
+ },
+
methods = {
/**
* Check the validity of the current field
@@ -231,14 +292,7 @@
return valid;
},
validate: function (settings) {
- // Get the HTML5 pattern attribute if it exists.
- // ** TODO: If a pattern class exists, grab the pattern from the patternLibrary, but the pattern attrib should override that value.
var $this = $(this),
- pattern = $this.filter('[pattern]')[0] ? $this.attr('pattern') : false,
-
- // The pattern attribute must match the whole value, not just a subset:
- // "...as if it implied a ^(?: at the start of the pattern and a )$ at the end."
- re = new RegExp('^(?:' + pattern + ')$'),
$radiosWithSameName = null,
value = ($this.is('[type=checkbox]')) ?
$this.is(':checked') : ($this.is('[type=radio]') ?
@@ -252,66 +306,53 @@
validClass = settings.validClass,
errorIDbare = $this.attr(settings.errorAttribute) || false, // Get the ID of the error element.
errorID = errorIDbare ? '#' + errorIDbare.replace(/(:|\.|\[|\])/g,'\\$1') : false, // Add the hash for convenience. This is done in two steps to avoid two attribute lookups.
- required = false,
- validity = createValidity({element: this, valid: true}),
- $checkRequired = $(''),
- maxlength;
-
- /* If the required attribute exists, set it required to true, unless it's set 'false'.
- * This is a minor deviation from the spec, but it seems some browsers have falsey
- * required values if the attribute is empty (should be true). The more conformant
- * version of this failed sanity checking in the browser environment.
- * This plugin is meant to be practical, not ideologically married to the spec.
- */
- // Feature fork
- if ($checkRequired.filter('[required]') && $checkRequired.filter('[required]').length) {
- required = ($this.filter('[required]').length && $this.attr('required') !== 'false');
- } else {
- required = ($this.attr('required') !== undefined);
- }
-
- if (settings.debug && window.console) {
- console.log('Validate called on "' + value + '" with regex "' + re + '". Required: ' + required); // **DEBUG
- console.log('Regex test: ' + re.test(value) + ', Pattern: ' + pattern); // **DEBUG
- }
-
- maxlength = parseInt($this.attr('maxlength'), 10);
- if (!isNaN(maxlength) && value.length > maxlength) {
- validity.valid = false;
- validity.tooLong = true;
- }
-
- if (required && !value) {
- validity.valid = false;
- validity.valueMissing = true;
- } else if (pattern && !re.test(value) && value) {
- validity.valid = false;
- validity.patternMismatch = true;
- } else {
- if (!settings.RODom) {
- settings.markValid({
- element: this,
- validity: validity,
- errorClass: errorClass,
- validClass: validClass,
- errorID: errorID,
- settings: settings
- });
+ validity = createValidity({element: this, valid: true});
+
+ // Iterate through the validators. If any fail, the field fails.
+ for (var i = 0;iHTMTL5 pattern attribute. e.g.
').h5Validate === 'function'), 'h5Validate exists');
});
+ test('Required validation failure flag false by default:', function () {
+ var $form = $(''),
+ $input = $('').appendTo($form);
+
+ $form.appendTo('body').h5Validate();
+
+ $input.val('Bob');
+ $input.on('validated',function(event,validity) {
+ strictEqual(validity.valueMissing, false, 'If the required validator does not fail, the validity property "valueMissing" should be false.');
+ });
+ $input.h5Validate('isValid');
+ });
+
+ test('Maxlength validation failure flag false by default:', function () {
+ var $form = $(''),
+ $input = $('').appendTo($form);
+
+ $form.appendTo('body').h5Validate();
+
+ $input.val('Bob');
+ $input.on('validated',function(event,validity) {
+ strictEqual(validity.tooLong, false, 'If the maxlength validator does not fail, the validity property "tooLong" should be false.');
+ });
+ $input.h5Validate('isValid');
+ });
+
+ test('Pattern validation failure flag false by default:', function () {
+ var $form = $(''),
+ $input = $('').appendTo($form);
+
+ $form.appendTo('body').h5Validate();
+
+ $input.val('Bob');
+ $input.on('validated',function(event,validity) {
+ strictEqual(validity.patternMismatch, false, 'If the pattern validator does not fail, the validity property "patternMismatch" should be false.');
+ });
+ $input.h5Validate('isValid');
+ });
+
test('Required:', function () {
var $input = $('#name');
ok((!$input.h5Validate('isValid')), 'Required input should be invalid when empty.');
@@ -17,6 +56,15 @@
$input.val('');
});
+ test('Required validation failure flag set:', function () {
+ var $input = $('#name');
+ $input.val('');
+ $input.on('validated',function(event,validity) {
+ ok(validity.valueMissing, 'If the required validator fails, the validity property "valueMissing" should be true.');
+ });
+ $input.h5Validate('isValid');
+ });
+
test('Pattern attribute:', function () {
var $input = $('#birthdate');
ok(($input.h5Validate('isValid')), 'Optional input should be valid when empty.');
@@ -26,6 +74,24 @@
ok((!$input.h5Validate('isValid')), 'Input should be invalid when given invalid input.');
});
+ test('Pattern validation failure flag set:', function () {
+ var $input = $('#birthdate');
+ $input.val('foo');
+ $input.on('validated',function(event,validity) {
+ ok(validity.patternMismatch, 'If the pattern validator fails, the validity property "patternMismatch" should be true.');
+ });
+ $input.h5Validate('isValid');
+ });
+
+ test('Maxlength validation failure flag set', function () {
+ var $input = $('#short-answer');
+ $input.val('something longer than 10 characters');
+ $input.on('validated',function(event,validity) {
+ ok(validity.tooLong, 'If the maxlength validator fails, the validity property "tooLong" should be true.');
+ });
+ $input.h5Validate('isValid');
+ });
+
test('Pattern library:', function () {
var $input = $('#email');
$input.val('test@example.com');
@@ -302,6 +368,128 @@
$form.empty().remove();
});
+ test('Adding an validator should fail for non functions',function () {
+ raises(function() {
+ $.h5Validate.addValidator('[name="target-field"]',5);
+ }, Error, 'Should not be able to add an integer as an validator.');
+
+ raises(function() {
+ $.h5Validate.addValidator('[name="target-field"]','5');
+ }, Error, 'Should not be able to add a string as an validator.');
+
+ raises(function() {
+ $.h5Validate.addValidator('[name="target-field"]',null);
+ }, Error, 'Should not be able to add a null as an validator.');
+
+ raises(function() {
+ $.h5Validate.addValidator('[name="target-field"]',{});
+ }, Error, 'Should not be able to add an object as an validator.');
+
+ raises(function() {
+ $.h5Validate.addValidator('[name="target-field"]',[]);
+ }, Error, 'Should not be able to add an array as an validator.');
+ });
+
+ test('Adding an validator should succeed for functions',function () {
+ $.h5Validate.addValidator('[name="target-field"]',function(){return true;});
+ ok(true, 'Should be able to add a function as an validator without causing an exception.');
+ });
+
+ test('Adding an validator that returns true should not cause validation to fail.',function () {
+ var $form = $('#validationForm'),
+ $field = $('#validationFieldPass');
+ $.h5Validate.addValidator($field,function(){return true;});
+ $form.h5Validate();
+ equal($field.h5Validate('isValid'), true, 'Adding an validator that returns true should not cause validation to fail.');
+ });
+
+ test('Adding an validator that returns false should cause validation to fail.',function () {
+ var $form = $('#validationForm'),
+ $field = $('#validationFieldFail');
+ $.h5Validate.addValidator($field,function(){return false;});
+ $form.h5Validate();
+ equal($field.h5Validate('isValid'), false, 'Adding an validator that returns false should cause validation to fail.');
+ });
+
+ test('Adding an validator that confirms the value of the input should not cause validation to fail.',function () {
+ var $form = $('#validationForm'),
+ $field = $('#validationFieldExample');
+ $.h5Validate.addValidator($field,function(value){return value == 'example';});
+ $form.h5Validate();
+ equal($field.h5Validate('isValid'), true, 'Adding an validator that confirms the value of the input should not cause validation to fail.');
+ });
+
+ test('Adding an validator that disconfirms the value of the input should cause validation to fail.',function () {
+ var $form = $('#validationForm'),
+ $field = $('#validationFieldNotExample');
+ $.h5Validate.addValidator($field,function(value){return value == 'example';});
+ $form.h5Validate();
+ equal($field.h5Validate('isValid'), false, 'Adding an validator that disconfirms the value of the input should cause validation to fail.');
+ });
+
+ test('Adding an validator to a selector that does not exist should not cause validation to fail.',function () {
+ var $form = $('#validationFormPass');
+ $.h5Validate.addValidator('atagthatdoesntexistinhtml',function(){return false;});
+ $form.h5Validate();
+ equal($form.h5Validate('allValid'), true, 'Adding an validator to a selector that does not exist should not cause validation to fail.');
+ });
+
+ test('validators should be passed the value of the DOM element.',function () {
+ var $form = $('#validationForm'),
+ $field = $('#validationFieldExample');
+ $.h5Validate.addValidator($field,function(value) {
+ equal(value, $field.val(), 'validators should be passed the value of the DOM element.');
+ return true;
+ });
+ $form.h5Validate();
+ $field.h5Validate('isValid');
+ });
+
+ test('validators should be run in the context of the DOM element of the target field.',function () {
+ var $form = $('#validationForm'),
+ $field = $('#validationFieldExample');
+ $.h5Validate.addValidator($field,function() {
+ equal(this, $field.get(0), 'validators should be run in the context of the DOM element of the target field.');
+ return true;
+ });
+ $form.h5Validate();
+ $field.h5Validate('isValid');
+ });
+
+ test('If an added validator fails, the validity "failedValidator" field should be true.',function () {
+ var $form = $('#validationForm'),
+ $field = $('#validationFieldNotExampleNotNamed');
+ $.h5Validate.addValidator($field,function(value){return value == 'example';});
+ $field.on('validated',function(event,validity) {
+ ok(validity.failedValidator, 'If an added validator fails, the validity "failedValidator" field should be true.');
+ });
+ $form.h5Validate();
+ $field.h5Validate('isValid');
+ });
+
+ test('If no validator name is supplied,the validity "failedValidatorNames" property should be null.',function () {
+ var $form = $('#validationForm'),
+ $field = $('#validationFieldNotExampleNotNamed');
+ $.h5Validate.addValidator($field,function(value){return value == 'example';});
+ $field.on('validated',function(event,validity) {
+ deepEqual(validity.failedValidatorNames, [], 'If no validator name is supplied,the validity "failedValidatorNames" property should be an empty array.');
+ });
+ $form.h5Validate();
+ $field.h5Validate('isValid');
+ });
+
+ test('Supplied validator names should be assigned to the validity "failedValidatorNames" property if they exist.',function () {
+ var $form = $('#validationForm'),
+ $field = $('#validationFieldNotExampleNamed'),
+ validatorName = 'exampleValidator';
+ $.h5Validate.addValidator($field,function(value){return value == 'example';},{name: validatorName});
+ $field.on('validated',function(event,validity) {
+ deepEqual(validity.failedValidatorNames, [validatorName], 'Supplied validator names should be assigned to the validity "failedValidatorNames" property if they exist.');
+ });
+ $form.h5Validate();
+ $field.h5Validate('isValid');
+ });
+
}
exports.runTests = runTests;
}((typeof exports !== 'undefined') ? exports : window));