This repository has been archived by the owner on Feb 10, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 125
External Validators #87
Open
christianlent
wants to merge
5
commits into
ericelliott:master
Choose a base branch
from
christianlent:external
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
88d89f8
Per-selector external validation
709dcbd
Fixed some external validation tests and added a few more.
e27e6ea
Renamed addExternalValidator to addValidator
4a827d1
Refactored some builtin validators to use new "validators" array
christianlent 4480079
Better default failure flags
christianlent File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 = $('<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 | ||
|
@@ -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 = $('<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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
|
||
} | ||
} | ||
} | ||
} | ||
|
||
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 | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the appropriate place to add our validators? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. =) |
||
|
||
return $.extend(settings, { | ||
activeClass: activeClass, | ||
|
@@ -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 || {}} ); | ||
} | ||
}; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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