Skip to content
This repository has been archived by the owner on Feb 10, 2019. It is now read-only.

External Validators #87

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 130 additions & 67 deletions jquery.h5validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand Down Expand Up @@ -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;
}
},
}
},

Expand All @@ -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) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the right name? I couldn't think of anything I liked better than "BuiltinValidators"

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a good reason to distinguish between "built in" validators and add-on / plugin validators?

It might be useful to treat them all the same way...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely! This pull request does treat them all the same way - the only difference here is that the "addBuiltinValidators" function is adding validators that are shipped with h5Validate (and therefore could be said to be, in one sense of the word, "builtin"). On the other hand, the function name could certainly be changed to something like "addValidators" or similar so as not to miscommunicate its purpose. Please advise

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 = $('<input required>'),
$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
Expand Down Expand Up @@ -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]') ?
Expand All @@ -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 = $('<input required>'),
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;i<settings.validators.length;i++) {
var validator = settings.validators[i].validator,
selector = settings.validators[i].selector,
options = settings.validators[i].options,
setFailureFlag = false;

// We don't want to overwrite any of the other validity properties (such as "valid")
if (options.validityFailureFlag && !validity.hasOwnProperty(options.validityFailureFlag)) {
setFailureFlag = true;
validity[options.validityFailureFlag] = false;
}

if ($(this).is(selector)) {
var boundValidator = validator.bind(this);
if (!boundValidator(value)) {
validity.valid = false;
validity.failedValidator = true;
if (options.name) {
validity.failedValidatorNames.push(options.name);
}
if (setFailureFlag) {
validity[options.validityFailureFlag] = true;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is potentially dangerous. If somebody sets a "validityFailureFlag" value of "valid" for instance, we might accidentally overwrite important data. There are a few solutions, none of which I love:

  • Do a hasOwnProperty check first, but that would only work if we don't set default value for those flags
  • Manually exclude "protected" validity properties, but that's another piece of code that needs to be maintained if the validity object changes
  • Get rid of validityFailureFlags (the same information is essentially available in "failedValidatorsNames"). Definitely not a great idea especially for a minor version increment.

}
}
}
}

if (!validity.valid) {
if (!settings.RODom) {
settings.markInvalid({
element: this,
validity: validity,
errorClass: errorClass,
validClass: validClass,
errorID: errorID,
settings: settings
});

if (!settings.RODom) {
var options = {
element: this,
validity: validity,
errorClass: errorClass,
validClass: validClass,
errorID: errorID,
settings: settings
};

if (validity.valid) {
settings.markValid(options);
} else {
settings.markInvalid(options);
}
}

$this.trigger('validated', validity);

// If it's a radio button, also validate the other radio buttons with the same name
Expand Down Expand Up @@ -458,6 +499,8 @@
// Combine defaults and options to get current settings.
var settings = $.extend({}, defaults, options, methods),
activeClass = settings.classPrefix + settings.activeClass;

addBuiltinValidators(settings);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the appropriate place to add our validators?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. =)


return $.extend(settings, {
activeClass: activeClass,
Expand Down Expand Up @@ -523,6 +566,26 @@
}
re = new RegExp('^(?:' + pattern + ')$');
$(selector).data('regex', re);
},
/**
* Takes a selector and a function that is used to do validation of all fields.
* This is useful for doing validation against a webservice
* (e.g. 'http://example.com/is_value_valid/example_value')
* Each validator will be matched using the corresponding selector against every field attached to h5Validate.
*
* @param {Selector} selector - A jquery selector of the field on which to apply the validator.
* example: [name="target-field"]
* @param {Function} validator - An validator function with the following definition:
* @param {String} value - The value of the input being validated.
* @return {Boolean} - The result of the validation (true = pass, false = fail).
* @this {DOM Element} - The dom element being validated.
* @throws {Error} - If the validator is not a function, throw an exception
*/
addValidator: function(selector, validator, options) {
if (!$.isFunction(validator)) {
throw new Error('Expected validator function. Saw ', validator);
}
defaults.validators.push({selector: selector, validator: validator, options: options || {}} );
}
};

Expand Down
22 changes: 21 additions & 1 deletion test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ <h3>HTMTL5 pattern attribute. e.g.</h3>
</form>
<p><em>Hint: It's expecting mm/dd/yyyy. Try typing "jQuery rocks!" instead.</em></p>


<h3>HTMTL5 maxlength attribute. e.g.</h3>

<form class="h5-defaults">
<label for="short-answer">Short Answer:</label>
<input id="short-answer" name="answer" type="text" placeholder="shortanswr" title="Short Answer" maxlength="10" />
</form>

<h3>Pattern Library</h3>

Expand Down Expand Up @@ -259,6 +264,21 @@ <h2>Bypassing Validation tests (via 'formnovalidate')</h2>
<input id="bypassSubmit" type="submit" formnovalidate="formnovalidate"/>
</form>
</section>

<section>
<h2>Form Validation Tests</h2>
<form id="validationForm">
<input id="validationFieldPass" type="text" />
<input id="validationFieldFail" type="text" />
<input id="validationFieldExample" type="text" value="example" />
<input id="validationFieldNotExample" type="text" value="notexample" />
<input id="validationFieldNotExampleNotNamed" type="text" value="notexample" />
<input id="validationFieldNotExampleNamed" type="text" value="notexample" />
</form>
<form id="validationFormPass">
<input id="validationFieldUnused" type="text" />
</form>
</section>


<script src="http://code.jquery.com/jquery.js"></script>
Expand Down
Loading