forked from mootools/mootools-more
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
NEW - A flexible Validation module with async support
- Loading branch information
Arian
committed
Apr 24, 2011
1 parent
d847456
commit 398b3d6
Showing
3 changed files
with
381 additions
and
2 deletions.
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 |
---|---|---|
@@ -0,0 +1,195 @@ | ||
/* | ||
--- | ||
name: Validation | ||
description: A Validation System | ||
license: MIT-style license | ||
authors: | ||
- Arian Stolwijk | ||
requires: | ||
- Core/Class | ||
- Core/Object | ||
provides: [Validation] | ||
... | ||
*/ | ||
|
||
(function(){ | ||
|
||
var rules = {}; | ||
|
||
var Validation = this.Validation = new Class({ | ||
|
||
Implements: [Options, Events], | ||
|
||
options: { | ||
stopOnFail: false | ||
}, | ||
|
||
rules: [], | ||
failed: [], | ||
|
||
initialize: function(rules, options){ | ||
this.setOptions(options); | ||
if (rules) this.addRules(Array.from(rules)); | ||
}, | ||
|
||
addRule: function(rule, options){ | ||
rule = Validation.lookupRule(rule); | ||
if (rule){ | ||
if (options) Object.merge(rule.options, options); | ||
this.rules.include(rule); | ||
} | ||
return this; | ||
}, | ||
|
||
addRules: function(rules){ | ||
for (var i = 0, l = rules.length; i < l; i++) this.addRule(rules[i]); | ||
return this; | ||
}, | ||
|
||
validate: function(value, options){ | ||
|
||
var old = this.options; | ||
options = Object.append({stopOnFail: old.stopOnFail}, options); | ||
|
||
var rules = this.rules, rule, length = rules.length, | ||
passed = [], progressed = [], failed = [], | ||
self = this; | ||
|
||
var progress = function(result){ | ||
if (!rule) return; | ||
|
||
if (!Type.isObject(result)) result = {passed: result}; | ||
Object.append(result, {rule: rule, name: rule.name, value: value, options: rule.options}); | ||
|
||
progressed.push(result); | ||
(result.passed ? passed : failed).push(result); | ||
self.fireEvent('progress', [result, progressed, passed, failed, rules]); | ||
if (passed.length == length){ // all rules passed | ||
self.fireEvent('success', [passed]); | ||
} else if ( | ||
(!result && options.stopOnFail) // first one failed | ||
|| (progressed.length == length) // all failed | ||
){ | ||
this.failed = failed; | ||
self.fireEvent('failure', [failed]); | ||
} else { // validate next rule | ||
validate(); | ||
} | ||
}; | ||
|
||
var validate = function(){ | ||
rule = rules[progressed.length]; | ||
if (rule.async) rule.rule.call(self, value, rule.options, progress); | ||
else progress(rule.rule.call(self, value, rule.options)); | ||
}; | ||
validate(); | ||
|
||
return !failed.length; | ||
}, | ||
|
||
getErrors: function(fn){ | ||
return Validation.report(this.failed, fn); | ||
} | ||
|
||
}).extend({ | ||
|
||
validate: function(rules, value, success, failure, progress, options){ | ||
if (arguments.length == 2 && typeOf(rules) != 'array'){ | ||
var rule = Validation.lookupRule(rules); | ||
if (!rule.async){ | ||
var result = rule.rule(value, rule.options); | ||
return (typeOf(result) == 'object') ? result.passed : result; | ||
} | ||
} | ||
options = Object.merge({}, options || {}, { | ||
onSuccess: success, | ||
onFailure: failure, | ||
onProgress: progress | ||
}); | ||
return (new Validation(rules, options)).validate(value); | ||
}, | ||
|
||
defineRule: function(name, rule, options){ | ||
rules[name] = Object.merge({ | ||
name: name, | ||
rule: rule, | ||
options: {} | ||
}, options); | ||
return this; | ||
}, | ||
|
||
defineRegExpRule: function(name, regexp, options){ | ||
return Validation.defineRule(name, function(value){ | ||
return regexp.test(value); | ||
}, options); | ||
}, | ||
|
||
defineAsyncRule: function(name, rule, options){ | ||
options = options || {}; | ||
options.async = true; | ||
return Validation.defineRule(name, rule, options); | ||
}, | ||
|
||
lookupRule: function(rule){ | ||
var type = typeOf(rule); | ||
if (type != 'object'){ | ||
switch (typeOf(rule)){ | ||
case 'string': return rules[rule]; | ||
case 'function': return {rule: rule}; | ||
} | ||
return null; | ||
} | ||
return (rule.name && !rule.rule) | ||
? Object.merge({}, rules[rule.name], rule) : rule; | ||
}, | ||
|
||
report: function(failed, fn){ | ||
return (fn ? failed.map(fn) : failed); | ||
} | ||
|
||
}); | ||
|
||
// Overload | ||
Validation.extend({ | ||
defineRules: Validation.defineRule.overloadSetter(true), | ||
defineRegExpRules: Validation.defineRegExpRule.overloadSetter(true), | ||
defineAsyncRules: Validation.defineAsyncRule.overloadSetter(true) | ||
}); | ||
|
||
// Defining some default rules | ||
Validation.defineRules({ | ||
|
||
empty: function(value){ | ||
return (value == null || value == ''); | ||
}, | ||
|
||
required: function(value){ | ||
return (value != null && value != ''); | ||
}, | ||
|
||
equals: function(value, options){ | ||
return (value == options.equals); | ||
}, | ||
|
||
between: function(value, options){ | ||
return (value > options.min && value < options.max); | ||
}, | ||
|
||
minLength: function(value, options){ | ||
return (value.length >= options.minLength); | ||
}, | ||
|
||
maxLength: function(value, options){ | ||
return (value.length <= options.maxLength); | ||
} | ||
|
||
}); | ||
|
||
})(); |
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 |
---|---|---|
@@ -0,0 +1,181 @@ | ||
/* | ||
Script: Validation.js | ||
Specs for Validation.js | ||
License: | ||
MIT-style license. | ||
*/ | ||
|
||
describe('Validation', function(){ | ||
|
||
it('should do a simple validation', function(){ | ||
var validation = new Validation('required'); | ||
expect(validation.validate('')).toBeFalsy(); | ||
expect(validation.validate('Moo!!')).toBeTruthy(); | ||
}); | ||
|
||
it('should add a rule', function(){ | ||
var validation = new Validation(); | ||
validation.addRule('empty'); | ||
expect(validation.rules.length).toEqual(1); | ||
}); | ||
|
||
it('should define, add and validate a rule with options', function(){ | ||
var spy = jasmine.createSpy('MyTestingRuleWithOptions'); | ||
Validation.defineRule('MyTestingRuleWithOptions', spy); | ||
|
||
var value = 'MyTestValue', options = {first: 1, second: 2, third: 3}; | ||
var validation = new Validation({ | ||
name: 'MyTestingRuleWithOptions', | ||
options: options | ||
}); | ||
validation.validate(value); | ||
|
||
expect(spy).toHaveBeenCalledWith(value, options); | ||
}); | ||
|
||
it('should fire the failure event with the failed rules', function(){ | ||
Validation.defineRules({ | ||
MyErrorNamesTest1: Function.from(false), | ||
MyErrorNamesTest2: Function.from(false), | ||
MyErrorNamesTest3: Function.from(false), | ||
MyErrorNamesTest4: Function.from(false) | ||
}); | ||
|
||
var ruleNames = [ | ||
'MyErrorNamesTest1', | ||
'MyErrorNamesTest2', | ||
'MyErrorNamesTest3', | ||
'MyErrorNamesTest4' | ||
]; | ||
|
||
var testOption = {foo: 'testOption'}; | ||
|
||
var val = new Validation(ruleNames.slice(0, 3)); | ||
val.addRule('MyErrorNamesTest4', testOption); | ||
|
||
var errors = []; | ||
val.addEvent('failure', function(rules){ | ||
errors = rules; | ||
}); | ||
|
||
var res = val.validate('foo'); | ||
|
||
expect(res).toEqual(false); | ||
|
||
errors.each(function(error, i){ | ||
expect(error.name).toEqual(ruleNames[i]); | ||
expect(error.value).toEqual('foo'); | ||
}); | ||
|
||
expect(errors[3].options).toEqual(testOption); | ||
}); | ||
|
||
it('should test the Validation.validate shortcut', function(){ | ||
expect(Validation.validate({ | ||
name: 'between', | ||
options: {min: 3, max: 6} | ||
}, 5)).toBeTruthy(); | ||
expect(Validation.validate('empty', '')).toBeTruthy(); | ||
expect(Validation.validate('empty', 'asdf')).toBeFalsy(); | ||
}); | ||
|
||
it('should validate when a defined rule returns a object', function(){ | ||
var returnedErrors = [1, 2, 3]; | ||
Validation.defineRule('ObjectRule', function(){ | ||
return {passed: false, errors: returnedErrors}; | ||
}); | ||
|
||
// Shortcut function | ||
expect(Validation.validate('ObjectRule', 'moo')).toBeFalsy(); | ||
|
||
// using the Validation Class | ||
var val = new Validation('ObjectRule'); | ||
expect(val.validate('moo')).toBeFalsy(); | ||
|
||
// Checking the errors | ||
val.getErrors().each(function(error, i){ | ||
expect(error.error).toEqual(returnedErrors[i]); | ||
expect(error.name).toEqual('ObjectRule'); | ||
expect(error.value).toEqual('moo'); | ||
}); | ||
}); | ||
|
||
describe('Async Rules', function(){ | ||
|
||
Validation.defineAsyncRule('async1', function(value, options, progress){ | ||
progress.delay(2, null, options.val && value == options.val); | ||
}); | ||
Validation.defineAsyncRule('async2', function(value, options, progress){ | ||
progress.delay(2, null, options.res); | ||
}); | ||
|
||
it('should validate the asyn rules', function(){ | ||
|
||
var success = jasmine.createSpy('success'), | ||
failure = jasmine.createSpy('failure'), | ||
progress = jasmine.createSpy('progress'); | ||
|
||
Validation.validate([ | ||
{name: 'async1', options: {val: 'foo'}}, | ||
{name: 'async2', options: {res: true}} | ||
], 'foo', success, failure, progress); | ||
|
||
waits(30); | ||
|
||
runs(function(){ | ||
expect(success).toHaveBeenCalled(); | ||
expect(failure).not.toHaveBeenCalled(); | ||
expect(progress.callCount).toEqual(2); | ||
}); | ||
|
||
}); | ||
|
||
}); | ||
|
||
describe('Default Rules', function(){ | ||
|
||
it('should test the empty rule', function(){ | ||
var val = new Validation('empty'); | ||
expect(val.validate('')).toBeTruthy(); | ||
expect(val.validate('meh')).toBeFalsy(); | ||
}); | ||
|
||
it('should test the required rule', function(){ | ||
var val = new Validation('required'); | ||
expect(val.validate('foo')).toBeTruthy(); | ||
expect(val.validate('')).toBeFalsy(); | ||
}); | ||
|
||
it('should test the equals rule', function(){ | ||
var val = new Validation(); | ||
val.addRule('equals', {equals: 'mootools'}); | ||
expect(val.validate('mootools')).toBeTruthy(); | ||
expect(val.validate('moo')).toBeFalsy(); | ||
}); | ||
|
||
it('should test the between rule', function(){ | ||
var validation = new Validation(); | ||
validation.addRule('between', {min: 1, max: 5}); | ||
expect(validation.validate(3)).toBeTruthy(); | ||
expect(validation.validate(0.5)).toBeFalsy(); | ||
expect(validation.validate(7)).toBeFalsy(); | ||
}); | ||
|
||
it('should test the minLength rule', function(){ | ||
var validation = new Validation(); | ||
validation.addRule('minLength', {minLength: 4}); | ||
expect(validation.validate('mooing')).toBeTruthy(); | ||
expect(validation.validate('moo')).toBeFalsy(); | ||
}); | ||
|
||
it('should test the maxLength rule', function(){ | ||
var validation = new Validation(); | ||
validation.addRule('maxLength', {maxLength: 4}); | ||
expect(validation.validate('moo')).toBeTruthy(); | ||
expect(validation.validate('mooing')).toBeFalsy(); | ||
}); | ||
|
||
}); | ||
|
||
}); |
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