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 3, 2016
1 parent 3ee0d97 commit 6b20b79
Show file tree
Hide file tree
Showing 41 changed files with 416 additions and 110 deletions.
2 changes: 1 addition & 1 deletion dist/amd/implementation/standard-validator.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ValidationMessageProvider } from './validation-messages';
* 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;
Expand Down
7 changes: 7 additions & 0 deletions dist/amd/implementation/standard-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ 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 object containing the field. get the last propertyy in the chain
//to get the field name. Use thi to get the correct value.
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
22 changes: 16 additions & 6 deletions dist/amd/implementation/validation-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,33 @@ define(["require", "exports", 'aurelia-binding', 'aurelia-templating', './util',
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 classic = /^function\s*\([$_\w\d]+\)\s*\{\s*(?:"use strict";)?\s*.*return\s+[$_\w\d]+((\.[$_\w\d]+)+)\s*;?\s*\}$/;
var arrow = /^\(?[$_\w\d]+\)?\s*=>\s*(?:\{?.*return\s+)?[$_\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]);
var name = match[1][0] == "." ? match[1].substr(1) : match[1];
return this.parser.parse(name);
};
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
};
}
Expand Down
1 change: 1 addition & 0 deletions dist/amd/property-info.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ import { Expression } from 'aurelia-binding';
export declare function getPropertyInfo(expression: Expression, source: any): {
object: any;
propertyName: string;
ruleSrc: null;
};
12 changes: 11 additions & 1 deletion dist/amd/property-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,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,7 +50,7 @@ define(["require", "exports", 'aurelia-binding'], function (require, exports, au
else {
throw new Error("Expression '" + originalExpression + "' is not compatible with the validate binding-behavior.");
}
return { object: object, propertyName: propertyName };
return { object: object, propertyName: propertyName, ruleSrc: ruleSrc };
}
exports.getPropertyInfo = getPropertyInfo;
});
1 change: 1 addition & 0 deletions dist/amd/validation-controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export declare class ValidationController {
*/
validateTrigger: number;
private finishValidating;
isValid: boolean;
constructor(validator: Validator);
/**
* Adds an object to the set of objects that should be validated when validate is called.
Expand Down
37 changes: 27 additions & 10 deletions dist/amd/validation-controller.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
define(["require", "exports", './validator', './validate-trigger', './property-info', './validation-error'], function (require, exports, validator_1, validate_trigger_1, property_info_1, validation_error_1) {
define(["require", "exports", './validator', './validate-trigger', './property-info', './validation-error', './implementation/rules'], function (require, exports, validator_1, validate_trigger_1, property_info_1, validation_error_1, rules_1) {
"use strict";
/**
* Orchestrates validation.
Expand Down Expand Up @@ -30,6 +30,7 @@ define(["require", "exports", './validator', './validate-trigger', './property-i
this.validateTrigger = validate_trigger_1.validateTrigger.blur;
// Promise that resolves when validation has completed.
this.finishValidating = Promise.resolve();
this.isValid = false;
}
/**
* Adds an object to the set of objects that should be validated when validate is called.
Expand Down Expand Up @@ -112,7 +113,7 @@ define(["require", "exports", './validator', './validate-trigger', './property-i
*/
ValidationController.prototype.getInstructionPredicate = function (instruction) {
if (instruction) {
var object_1 = instruction.object, propertyName_1 = instruction.propertyName, rules_1 = instruction.rules;
var object_1 = instruction.object, propertyName_1 = instruction.propertyName, rules_2 = instruction.rules;
var predicate_1;
if (instruction.propertyName) {
predicate_1 = function (x) { return x.object === object_1 && x.propertyName === propertyName_1; };
Expand All @@ -121,8 +122,8 @@ define(["require", "exports", './validator', './validate-trigger', './property-i
predicate_1 = function (x) { return x.object === object_1; };
}
// todo: move to Validator interface:
if (rules_1 && rules_1.indexOf) {
return function (x) { return predicate_1(x) && rules_1.indexOf(x.rule) !== -1; };
if (rules_2 && rules_2.indexOf) {
return function (x) { return predicate_1(x) && rules_2.indexOf(x.rule) !== -1; };
}
return predicate_1;
}
Expand All @@ -139,17 +140,20 @@ define(["require", "exports", './validator', './validate-trigger', './property-i
// Get a function that will process the validation instruction.
var execute;
if (instruction) {
var object_2 = instruction.object, propertyName_2 = instruction.propertyName, rules_2 = instruction.rules;
var object_2 = instruction.object, propertyName_2 = instruction.propertyName, rules_3 = instruction.rules;
// if rules were not specified, check the object map.
rules_2 = rules_2 || this.objects.get(object_2);
rules_3 = rules_3 || this.objects.get(object_2);
if (!rules_3) {
rules_3 = rules_1.Rules.get(ruleSrc);
}
// property specified?
if (instruction.propertyName === undefined) {
// validate the specified object.
execute = function () { return _this.validator.validateObject(object_2, rules_2); };
execute = function () { return _this.validator.validateObject(object_2, rules_3); };
}
else {
// validate the specified property.
execute = function () { return _this.validator.validateProperty(object_2, propertyName_2, rules_2); };
execute = function () { return _this.validator.validateProperty(object_2, propertyName_2, rules_3); };
}
}
else {
Expand All @@ -166,6 +170,15 @@ define(["require", "exports", './validator', './validate-trigger', './property-i
if (_this.objects.has(object)) {
continue;
}
if (propertyName.indexOf(".") !== -1) {
var parentProp = "";
var ittr = binding.sourceExpression.expression;
while (ittr.object) {
ittr = ittr.object;
parentProp = ittr.name;
}
rules = rules_1.Rules.get(binding._observer0._callable0._observer0.obj[parentProp]);
}
promises.push(_this.validator.validateProperty(object, propertyName, rules));
}
return Promise.all(promises).then(function (errorSets) { return errorSets.reduce(function (a, b) { return a.concat(b); }, []); });
Expand Down Expand Up @@ -241,7 +254,7 @@ define(["require", "exports", './validator', './validate-trigger', './property-i
this_1.errors.splice(this_1.errors.indexOf(oldError), 1);
}
else {
// there is a corresponding new error...
// there is a corresponding new error...
var newError = newErrors.splice(newErrorIndex, 1)[0];
// get the elements that are associated with the new error.
var elements_1 = this_1.getAssociatedElements(newError);
Expand Down Expand Up @@ -279,9 +292,13 @@ define(["require", "exports", './validator', './validate-trigger', './property-i
if (!binding.isBound) {
return;
}
var _a = property_info_1.getPropertyInfo(binding.sourceExpression, binding.source), object = _a.object, propertyName = _a.propertyName;
var _a = property_info_1.getPropertyInfo(binding.sourceExpression, binding.source), object = _a.object, propertyName = _a.propertyName, ruleSrc = _a.ruleSrc;
var registeredBinding = this.bindings.get(binding);
var rules = registeredBinding ? registeredBinding.rules : undefined;
if (!rules && ruleSrc) {
//if we got ruleSrc back we need to get the rules for the subprop which are located in the root of the model
rules = rules_1.Rules.get(ruleSrc);
}
this.validate({ object: object, propertyName: propertyName, rules: rules });
};
/**
Expand Down
2 changes: 1 addition & 1 deletion dist/commonjs/implementation/standard-validator.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ValidationMessageProvider } from './validation-messages';
* 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;
Expand Down
7 changes: 7 additions & 0 deletions dist/commonjs/implementation/standard-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ var StandardValidator = (function (_super) {
}
// 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 object containing the field. get the last propertyy in the chain
//to get the field name. Use thi to get the correct value.
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
22 changes: 16 additions & 6 deletions dist/commonjs/implementation/validation-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,33 @@ var ValidationParser = (function () {
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 classic = /^function\s*\([$_\w\d]+\)\s*\{\s*(?:"use strict";)?\s*.*return\s+[$_\w\d]+((\.[$_\w\d]+)+)\s*;?\s*\}$/;
var arrow = /^\(?[$_\w\d]+\)?\s*=>\s*(?:\{?.*return\s+)?[$_\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]);
var name = match[1][0] == "." ? match[1].substr(1) : match[1];
return this.parser.parse(name);
};
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
};
}
Expand Down
1 change: 1 addition & 0 deletions dist/commonjs/property-info.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ import { Expression } from 'aurelia-binding';
export declare function getPropertyInfo(expression: Expression, source: any): {
object: any;
propertyName: string;
ruleSrc: null;
};
12 changes: 11 additions & 1 deletion dist/commonjs/property-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,23 @@ function getPropertyInfo(expression, source) {
}
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 +50,6 @@ function getPropertyInfo(expression, source) {
else {
throw new Error("Expression '" + originalExpression + "' is not compatible with the validate binding-behavior.");
}
return { object: object, propertyName: propertyName };
return { object: object, propertyName: propertyName, ruleSrc: ruleSrc };
}
exports.getPropertyInfo = getPropertyInfo;
1 change: 1 addition & 0 deletions dist/commonjs/validation-controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export declare class ValidationController {
*/
validateTrigger: number;
private finishValidating;
isValid: boolean;
constructor(validator: Validator);
/**
* Adds an object to the set of objects that should be validated when validate is called.
Expand Down
Loading

0 comments on commit 6b20b79

Please sign in to comment.