Skip to content

Commit

Permalink
enhance(ValidationParser): handle validate props that subpropeties of…
Browse files Browse the repository at this point in the history
… an object

fixes aurelia#283
  • Loading branch information
ericIMT committed Oct 18, 2016
1 parent 80315ab commit 3b62826
Show file tree
Hide file tree
Showing 91 changed files with 1,251 additions and 541 deletions.
2 changes: 1 addition & 1 deletion dist/amd/implementation/rules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export declare class Rules {
/**
* The name of the property that stores the rules.
*/
static key: string;
private static key;
/**
* Applies the rules to a target.
*/
Expand Down
15 changes: 11 additions & 4 deletions dist/amd/implementation/standard-validator.d.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { ViewResources } from 'aurelia-templating';
import { Validator } from '../validator';
import { ValidationError } from '../validation-error';
import { Rule } from './rule';
import { ValidationMessageProvider } from './validation-messages';
/**
* Validates.
* Responsible for validating objects and properties.
*/
export declare class StandardValidator extends Validator {
static inject: (typeof ValidationMessageProvider | typeof ViewResources)[];
static inject: (typeof ViewResources | typeof ValidationMessageProvider)[];
private messageProvider;
private lookupFunctions;
private getDisplayName;
constructor(messageProvider: ValidationMessageProvider, resources: ViewResources);
private getMessage(rule, object, value);
private validateRuleSequence(object, propertyName, ruleSequence, sequence);
private validate(object, propertyName, rules);
/**
* Validates the specified property.
* @param object The object to validate.
Expand All @@ -30,4 +28,13 @@ export declare class StandardValidator extends Validator {
* for the object created by ValidationRules....on(class/object)
*/
validateObject(object: any, rules?: any): Promise<ValidationError[]>;
/**
* Determines whether a rule exists in a set of rules.
* @param rules The rules to search.
* @parem rule The rule to find.
*/
ruleExists(rules: Rule<any, any>[][], rule: Rule<any, any>): boolean;
private getMessage(rule, object, value);
private validateRuleSequence(object, propertyName, ruleSequence, sequence);
private validate(object, propertyName, rules);
}
60 changes: 41 additions & 19 deletions dist/amd/implementation/standard-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,39 @@ define(["require", "exports", 'aurelia-templating', '../validator', '../validati
this.lookupFunctions = resources.lookupFunctions;
this.getDisplayName = messageProvider.getDisplayName.bind(messageProvider);
}
/**
* Validates the specified property.
* @param object The object to validate.
* @param propertyName The name of the property to validate.
* @param rules Optional. If unspecified, the rules will be looked up using the metadata
* for the object created by ValidationRules....on(class/object)
*/
StandardValidator.prototype.validateProperty = function (object, propertyName, rules) {
return this.validate(object, propertyName, rules || null);
};
/**
* Validates all rules for specified object and it's properties.
* @param object The object to validate.
* @param rules Optional. If unspecified, the rules will be looked up using the metadata
* for the object created by ValidationRules....on(class/object)
*/
StandardValidator.prototype.validateObject = function (object, rules) {
return this.validate(object, null, rules || null);
};
/**
* Determines whether a rule exists in a set of rules.
* @param rules The rules to search.
* @parem rule The rule to find.
*/
StandardValidator.prototype.ruleExists = function (rules, rule) {
var i = rules.length;
while (i--) {
if (rules[i].indexOf(rule) !== -1) {
return true;
}
}
return false;
};
StandardValidator.prototype.getMessage = function (rule, object, value) {
var expression = rule.message || this.messageProvider.getMessage(rule.messageKey);
var _a = rule.property, propertyName = _a.name, displayName = _a.displayName;
Expand Down Expand Up @@ -53,6 +86,14 @@ define(["require", "exports", 'aurelia-templating', '../validator', '../validati
}
// validate.
var value = rule.property.name === null ? object : object[rule.property.name];
if (rule.property.name && rule.property.name.indexOf('.') !== -1) {
//if the rule name has a '.', we have a sub property.
//"Object" is the parent containing the field.
//The field is the last part of the propert path
//e.g. finalProp in object.sub1.sub2.finalProp
var parts = rule.property.name.split('.');
value = object[parts[parts.length - 1]];
}
var promiseOrBoolean = rule.condition(value, object);
if (!(promiseOrBoolean instanceof Promise)) {
promiseOrBoolean = Promise.resolve(promiseOrBoolean);
Expand Down Expand Up @@ -88,25 +129,6 @@ define(["require", "exports", 'aurelia-templating', '../validator', '../validati
}
return this.validateRuleSequence(object, propertyName, rules, 0);
};
/**
* Validates the specified property.
* @param object The object to validate.
* @param propertyName The name of the property to validate.
* @param rules Optional. If unspecified, the rules will be looked up using the metadata
* for the object created by ValidationRules....on(class/object)
*/
StandardValidator.prototype.validateProperty = function (object, propertyName, rules) {
return this.validate(object, propertyName, rules || null);
};
/**
* Validates all rules for specified object and it's properties.
* @param object The object to validate.
* @param rules Optional. If unspecified, the rules will be looked up using the metadata
* for the object created by ValidationRules....on(class/object)
*/
StandardValidator.prototype.validateObject = function (object, rules) {
return this.validate(object, null, rules || null);
};
StandardValidator.inject = [validation_messages_1.ValidationMessageProvider, aurelia_templating_1.ViewResources];
return StandardValidator;
}(validator_1.Validator));
Expand Down
4 changes: 2 additions & 2 deletions dist/amd/implementation/validation-parser.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export declare class ValidationParser {
private undefinedExpression;
private cache;
constructor(parser: Parser, bindinqLanguage: BindingLanguage);
private coalesce(part);
parseMessage(message: string): Expression;
private getAccessorExpression(fn);
parseProperty<TObject, TValue>(property: string | PropertyAccessor<TObject, TValue>): RuleProperty;
private coalesce(part);
private getAccessorExpression(fn);
}
export declare class MessageExpressionValidator extends Unparser {
private originalMessage;
Expand Down
41 changes: 25 additions & 16 deletions dist/amd/implementation/validation-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ define(["require", "exports", 'aurelia-binding', 'aurelia-templating', './util',
this.undefinedExpression = new aurelia_binding_1.LiteralPrimitive(undefined);
this.cache = {};
}
ValidationParser.prototype.coalesce = function (part) {
// part === null || part === undefined ? '' : part
return new aurelia_binding_1.Conditional(new aurelia_binding_1.Binary('||', new aurelia_binding_1.Binary('===', part, this.nullExpression), new aurelia_binding_1.Binary('===', part, this.undefinedExpression)), this.emptyStringExpression, new aurelia_binding_1.CallMember(part, 'toString', []));
};
ValidationParser.prototype.parseMessage = function (message) {
if (this.cache[message] !== undefined) {
return this.cache[message];
Expand All @@ -34,29 +30,42 @@ define(["require", "exports", 'aurelia-binding', 'aurelia-templating', './util',
this.cache[message] = expression;
return expression;
};
ValidationParser.prototype.getAccessorExpression = function (fn) {
var classic = /^function\s*\([$_\w\d]+\)\s*\{\s*(?:"use strict";)?\s*return\s+[$_\w\d]+\.([$_\w\d]+)\s*;?\s*\}$/;
var arrow = /^[$_\w\d]+\s*=>\s*[$_\w\d]+\.([$_\w\d]+)$/;
var match = classic.exec(fn) || arrow.exec(fn);
if (match === null) {
throw new Error("Unable to parse accessor function:\n" + fn);
}
return this.parser.parse(match[1]);
};
ValidationParser.prototype.parseProperty = function (property) {
if (util_1.isString(property)) {
return { name: property, displayName: null };
}
var accessor = this.getAccessorExpression(property.toString());
if (accessor instanceof aurelia_binding_1.AccessScope
|| accessor instanceof aurelia_binding_1.AccessMember && accessor.object instanceof aurelia_binding_1.AccessScope) {
var isSubProp = accessor instanceof aurelia_binding_1.AccessMember && accessor.object instanceof aurelia_binding_1.AccessScope;
if (accessor instanceof aurelia_binding_1.AccessScope || isSubProp) {
var propName = accessor.name;
if (isSubProp) {
//iterate up the chain until we are in the 1st sub-object of the root object.
var ao = accessor.object;
while (ao) {
propName = ao.name + '.' + propName;
ao = ao.object;
}
}
return {
name: accessor.name,
name: propName,
displayName: null
};
}
throw new Error("Invalid subject: \"" + accessor + "\"");
};
ValidationParser.prototype.coalesce = function (part) {
// part === null || part === undefined ? '' : part
return new aurelia_binding_1.Conditional(new aurelia_binding_1.Binary('||', new aurelia_binding_1.Binary('===', part, this.nullExpression), new aurelia_binding_1.Binary('===', part, this.undefinedExpression)), this.emptyStringExpression, new aurelia_binding_1.CallMember(part, 'toString', []));
};
ValidationParser.prototype.getAccessorExpression = function (fn) {
var classic = /^function\s*\([$_\w\d]+\)\s*\{\s*(?:"use strict";)?\s*return\s+[$_\w\d]+\.([$_\w\d]+(\.[$_\w\d]+)*)\s*;?\s*\}$/;
var arrow = /^\(?[$_\w\d]+\)?\s*=>\s*(?:\{?.*return\s+)?[$_\w\d]+\.([$_\w\d]+(\.[$_\w\d]+)*);?\s*\}?$/;
var match = classic.exec(fn) || arrow.exec(fn);
if (match === null) {
throw new Error("Unable to parse accessor function:\n" + fn);
}
return this.parser.parse(match[1]);
};
ValidationParser.inject = [aurelia_binding_1.Parser, aurelia_templating_1.BindingLanguage];
return ValidationParser;
}());
Expand Down
6 changes: 4 additions & 2 deletions dist/amd/implementation/validation-rules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ export declare class FluentEnsure<TObject> {
constructor(parser: ValidationParser);
/**
* Target a property with validation rules.
* @param property The property to target. Can be the property name or a property accessor function.
* @param property The property to target. Can be the property name or a property accessor
* function.
*/
ensure<TValue>(property: string | PropertyAccessor<TObject, TValue>): FluentRules<TObject, TValue>;
/**
Expand Down Expand Up @@ -233,7 +234,8 @@ export declare class ValidationRules {
* @param name The name of the custom rule. Also serves as the message key.
* @param condition The rule function.
* @param message The message expression
* @param argsToConfig A function that maps the rule's arguments to a "config" object that can be used when evaluating the message expression.
* @param argsToConfig A function that maps the rule's arguments to a "config"
* object that can be used when evaluating the message expression.
*/
static customRule(name: string, condition: (value: any, object?: any, ...args: any[]) => boolean | Promise<boolean>, message: string, argsToConfig?: (...args: any[]) => any): void;
/**
Expand Down
12 changes: 9 additions & 3 deletions dist/amd/implementation/validation-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ define(["require", "exports", './util', './rules', './validation-messages'], fun
* null, undefined and empty-string values are considered valid.
*/
FluentRules.prototype.email = function () {
return this.matches(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i)
// regex from https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
/* tslint:disable:max-line-length */
return this.matches(/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/)
.withMessageKey('email');
};
/**
Expand Down Expand Up @@ -301,17 +303,20 @@ define(["require", "exports", './util', './rules', './validation-messages'], fun
* Part of the fluent rule API. Enables targeting properties and objects with rules.
*/
var FluentEnsure = (function () {
/* tslint:enable */
function FluentEnsure(parser) {
this.parser = parser;
/**
* Rules that have been defined using the fluent API.
*/
this.rules = [];
/* tslint:disable */
this._sequence = 0;
}
/**
* Target a property with validation rules.
* @param property The property to target. Can be the property name or a property accessor function.
* @param property The property to target. Can be the property name or a property accessor
* function.
*/
FluentEnsure.prototype.ensure = function (property) {
this.assertInitialized();
Expand Down Expand Up @@ -377,7 +382,8 @@ define(["require", "exports", './util', './rules', './validation-messages'], fun
* @param name The name of the custom rule. Also serves as the message key.
* @param condition The rule function.
* @param message The message expression
* @param argsToConfig A function that maps the rule's arguments to a "config" object that can be used when evaluating the message expression.
* @param argsToConfig A function that maps the rule's arguments to a "config"
* object that can be used when evaluating the message expression.
*/
ValidationRules.customRule = function (name, condition, message, argsToConfig) {
validation_messages_1.validationMessages[name] = message;
Expand Down
4 changes: 2 additions & 2 deletions dist/amd/property-info.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import { Expression } from 'aurelia-binding';
* @param source The scope
*/
export declare function getPropertyInfo(expression: Expression, source: any): {
object: any;
object: Object;
propertyName: string;
};
} | null;
25 changes: 17 additions & 8 deletions dist/amd/property-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ define(["require", "exports", 'aurelia-binding'], function (require, exports, au
"use strict";
function getObject(expression, objectExpression, source) {
var value = objectExpression.evaluate(source, null);
if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
if (value === null || value === undefined || value instanceof Object) {
return value;
}
if (value === null) {
value = 'null';
}
else if (value === undefined) {
value = 'undefined';
}
throw new Error("The '" + objectExpression + "' part of '" + expression + "' evaluates to " + value + " instead of an object.");
/* tslint:disable */
throw new Error("The '" + objectExpression + "' part of '" + expression + "' evaluates to " + value + " instead of an object, null or undefined.");
/* tslint:enable */
}
/**
* Retrieves the object and property name for the specified expression.
Expand All @@ -25,13 +21,23 @@ define(["require", "exports", 'aurelia-binding'], function (require, exports, au
}
var object;
var propertyName;
var ruleSrc = null;
if (expression instanceof aurelia_binding_1.AccessScope) {
object = source.bindingContext;
propertyName = expression.name;
}
else if (expression instanceof aurelia_binding_1.AccessMember) {
object = getObject(originalExpression, expression.object, source);
propertyName = expression.name;
if (expression.object) {
//build the path to the property from the object root.
var exp = expression.object;
while (exp.object) {
propertyName = exp.name + '.' + propertyName;
exp = exp.object;
}
ruleSrc = getObject(originalExpression, exp, source);
}
}
else if (expression instanceof aurelia_binding_1.AccessKeyed) {
object = getObject(originalExpression, expression.object, source);
Expand All @@ -40,6 +46,9 @@ define(["require", "exports", 'aurelia-binding'], function (require, exports, au
else {
throw new Error("Expression '" + originalExpression + "' is not compatible with the validate binding-behavior.");
}
if (object === null || object === undefined) {
return null;
}
return { object: object, propertyName: propertyName };
}
exports.getPropertyInfo = getPropertyInfo;
Expand Down
10 changes: 5 additions & 5 deletions dist/amd/validate-binding-behavior-base.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ export declare abstract class ValidateBindingBehaviorBase {
constructor(taskQueue: TaskQueue);
protected abstract getValidateTrigger(controller: ValidationController): number;
/**
* Gets the DOM element associated with the data-binding. Most of the time it's
* the binding.target but sometimes binding.target is an aurelia custom element,
* or custom attribute which is a javascript "class" instance, so we need to use
* the controller's container to retrieve the actual DOM element.
*/
* Gets the DOM element associated with the data-binding. Most of the time it's
* the binding.target but sometimes binding.target is an aurelia custom element,
* or custom attribute which is a javascript "class" instance, so we need to use
* the controller's container to retrieve the actual DOM element.
*/
getTarget(binding: any, view: any): any;
bind(binding: any, source: any, rulesOrController?: ValidationController | any, rules?: any): void;
unbind(binding: any): void;
Expand Down
Loading

0 comments on commit 3b62826

Please sign in to comment.