Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

NEW - A flexible Validation module with async support #238

Open
wants to merge 1 commit into from

4 participants

@arian
Owner

Like you might know, I started a while ago writing a validation class, but now I'd like to make a pull request to get some more feedback + maybe an approve eventually.

Features include:

  • Adding multiple rules
  • Return an object with failed rules and some useful properties
  • Async support, so you could write a rule/validator which makes a Request
  • A fast Validation.validate shortcut
  • Some default validators

This is part 1 of all the stuff I've been working on, but this is the base class for now which is hopefully easier to review than everything in one time. The whole branch can be found here: https://github.com/arian/mootools-more/tree/validation or https://github.com/arian/mootools-more/compare/validation

Other features are:

  • Localized support (including localized rules)
  • A JSON Scheme validator
  • Even more Validators

TODOs

  • Write docs for them (when we've agreed on the API)
  • Do pull requests for the other parts
  • Integrate with Form.Validator
@anutron
Owner

Just getting to this (sorry, life happens). I like the abstraction. The intention here is to yank out some of this stuff from FormValidator and have it power that class? If so, I like.

@timwienk
Owner

The idea is to have an abstract Validation class, which is useable in any environment. The Form.Validator is then supposed to be just an implementation of Validation aimed at form elements. That way you'd be able to use the same Validation Class and rules in the browser and on the server-side.

@arian
Owner

That's the idea indeed, to have a bit lower level validation class which can be used in various cases and environments. Probably we need to write some specs for Form.Validator to make sure it won't break when using this in it. But like I said, I have some more stuff for this validator, and we could start with this pull request and add Form.Request and other stuff later on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 24, 2011
  1. @arian
This page is out of date. Refresh to see the latest.
View
195 Source/Validation/Validation.js.js
@@ -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);
+ }
+
+});
+
+})();
View
181 Specs/1.3/Validation/Validation.js.js
@@ -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();
+ });
+
+ });
+
+});
View
7 Specs/Configuration.js
@@ -44,7 +44,8 @@ Configuration.sets = {
'Types/String.Extras', 'Types/String.QueryString', 'Types/Number.Format',
'Types/Hash', 'Types/Hash.Extras', 'Types/Date',
'Types/Date.Extras', 'Locale/Locale',
- 'Utilities/Color', 'Utilities/Group', 'Utilities/Table'
+ 'Utilities/Color', 'Utilities/Group', 'Utilities/Table',
+ 'Validation/Validation'
]
},
@@ -154,7 +155,9 @@ Configuration.source = {
'Utilities/Color',
'Utilities/Group',
- 'Utilities/Table'
+ 'Utilities/Table',
+
+ 'Validation/Validation'
]
},
Something went wrong with that request. Please try again.