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 Dec 19, 2016
1 parent d749cf5 commit 8dbc4a0
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 149 deletions.
2 changes: 1 addition & 1 deletion doc/api.json

Large diffs are not rendered by default.

256 changes: 127 additions & 129 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,129 +1,127 @@
{
"name": "aurelia-validation",
"version": "1.0.0-beta.1.0.0",
"description": "Validation for Aurelia applications",
"keywords": [
"aurelia",
"plugin",
"validation"
],
"homepage": "http://aurelia.io",
"bugs": {
"url": "https://github.com/aurelia/validation/issues"
},
"license": "MIT",
"author": "Jeremy Danyow <[email protected]> (https:/danyow.net/)",
"main": "dist/commonjs/aurelia-validation.js",
"typings": "dist/commonjs/aurelia-validation.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/aurelia/validation"
},
"scripts": {
"lint": "cross-env tslint --project tsconfig.json",

"pretest": "cross-env npm run lint",
"test": "cross-env tsc && karma start --single-run",
"test-watch": "concurrently \"./node_modules/.bin/tsc --watch\" \"./node_modules/.bin/karma start\"",

"prebuild:amd": "cross-env rimraf dist/amd",
"build:amd": "cross-env tsc --project tsconfig.build.json --outDir dist/amd --module amd",
"postbuild:amd": "cross-env copyfiles --up 1 src/**/*.html src/**/*.css dist/amd",

"prebuild:commonjs": "cross-env rimraf dist/commonjs",
"build:commonjs": "cross-env tsc --project tsconfig.build.json --outDir dist/commonjs --module commonjs",
"postbuild:commonjs": "cross-env copyfiles --up 1 src/**/*.html src/**/*.css dist/commonjs",

"prebuild:es2015": "cross-env rimraf dist/es2015",
"build:es2015": "cross-env tsc --project tsconfig.build.json --outDir dist/es2015 --module es2015 --target es2015",
"postbuild:es2015": "cross-env copyfiles --up 1 src/**/*.html src/**/*.css dist/es2015",

"prebuild:native-modules": "cross-env rimraf dist/native-modules",
"build:native-modules": "cross-env tsc --project tsconfig.build.json --outDir dist/native-modules --module es2015",
"postbuild:native-modules": "cross-env copyfiles --up 1 src/**/*.html src/**/*.css dist/native-modules",

"prebuild:system": "cross-env rimraf dist/system",
"build:system": "cross-env tsc --project tsconfig.build.json --outDir dist/system --module system",
"postbuild:system": "cross-env copyfiles --up 1 src/**/*.html src/**/*.css dist/system",

"prebuild": "cross-env rimraf dist",
"build": "concurrently \"npm run build:amd\" \"npm run build:commonjs\" \"npm run build:es2015\" \"npm run build:native-modules\" \"npm run build:system\"",
"postbuild": "npm run build:doc",

"prebuild:doc": "cross-env rimraf doc/api.json && rimraf dist/doc-temp && tsc --project tsconfig.build.json --outFile dist/doc-temp/index.js && node doc/shape-defs",
"build:doc": "cross-env typedoc --json doc/api.json --excludeExternals --includeDeclarations --mode modules --target ES6 --name aurelia-ux-docs --ignoreCompilerErrors --tsconfig doc/tsconfig.json dist/doc-temp/",
"postbuild:doc": "cross-env node doc/shape-doc && rimraf dist/doc-temp"
},
"jspm": {
"registry": "npm",
"jspmPackage": true,
"main": "aurelia-validation",
"format": "amd",
"directories": {
"dist": "dist/amd"
},
"peerDependencies": {
"aurelia-binding": "^1.0.4",
"aurelia-dependency-injection": "^1.0.0",
"aurelia-logging": "^1.0.0",
"aurelia-pal": "^1.0.0",
"aurelia-task-queue": "^1.0.0",
"aurelia-templating": "^1.1.0"
},
"dependencies": {
"aurelia-binding": "^1.0.4",
"aurelia-dependency-injection": "^1.0.0",
"aurelia-logging": "^1.0.0",
"aurelia-pal": "^1.0.0",
"aurelia-task-queue": "^1.0.0",
"aurelia-templating": "^1.1.0"
},
"devDependencies": {}
},
"dependencies": {
"aurelia-binding": "^1.0.4",
"aurelia-dependency-injection": "^1.0.0",
"aurelia-logging": "^1.0.0",
"aurelia-pal": "^1.0.0",
"aurelia-task-queue": "^1.0.0",
"aurelia-templating": "^1.1.0"
},
"devDependencies": {
"aurelia-bootstrapper": "^1.0.0",
"aurelia-pal-browser": "^1.0.0",
"aurelia-polyfills": "^1.1.0",
"aurelia-testing": "^1.0.0-beta.2.0.0",
"concurrently": "^2.2.0",
"copyfiles": "^1.0.0",
"cross-env": "^2.0.1",
"jasmine-core": "^2.4.1",
"karma": "^1.2.0",
"karma-chrome-launcher": "^2.0.0",
"karma-ie-launcher": "^1.0.0",
"karma-jasmine": "^1.0.2",
"karma-requirejs": "^1.0.0",
"requirejs": "^2.2.0",
"requirejs-text": "^2.0.12",
"rimraf": "^2.5.4",
"tslint": "^3.15.1",
"typedoc": "^0.5.0",
"typescript": "next"
},
"aurelia": {
"build": {
"resources": [
"validate-binding-behavior",
"validation-errors-custom-attribute",
"validation-renderer-custom-attribute"
]
},
"documentation": {
"articles": [
{
"title": "Validation: Basics",
"href": "doc/article/en-US/validation-basics.md"
}
]
}
}
}
{
"name": "aurelia-validation",
"version": "1.0.0-beta.1.0.0",
"description": "Validation for Aurelia applications",
"keywords": [
"aurelia",
"plugin",
"validation"
],
"homepage": "http://aurelia.io",
"bugs": {
"url": "https://github.com/aurelia/validation/issues"
},
"license": "MIT",
"author": "Jeremy Danyow <[email protected]> (https:/danyow.net/)",
"main": "dist/commonjs/aurelia-validation.js",
"typings": "dist/commonjs/aurelia-validation.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/aurelia/validation"
},
"scripts": {
"lint": "cross-env tslint --project tsconfig.json",
"pretest": "cross-env npm run lint",
"test": "cross-env tsc && karma start --single-run",
"test-watch": "concurrently \"./node_modules/.bin/tsc --watch\" \"./node_modules/.bin/karma start\"",
"prebuild:amd": "cross-env rimraf dist/amd",
"build:amd": "cross-env tsc --project tsconfig.build.json --outDir dist/amd --module amd",
"postbuild:amd": "cross-env copyfiles --up 1 src/**/*.html src/**/*.css dist/amd",
"prebuild:commonjs": "cross-env rimraf dist/commonjs",
"build:commonjs": "cross-env tsc --project tsconfig.build.json --outDir dist/commonjs --module commonjs",
"postbuild:commonjs": "cross-env copyfiles --up 1 src/**/*.html src/**/*.css dist/commonjs",
"prebuild:es2015": "cross-env rimraf dist/es2015",
"build:es2015": "cross-env tsc --project tsconfig.build.json --outDir dist/es2015 --module es2015 --target es2015",
"postbuild:es2015": "cross-env copyfiles --up 1 src/**/*.html src/**/*.css dist/es2015",
"prebuild:native-modules": "cross-env rimraf dist/native-modules",
"build:native-modules": "cross-env tsc --project tsconfig.build.json --outDir dist/native-modules --module es2015",
"postbuild:native-modules": "cross-env copyfiles --up 1 src/**/*.html src/**/*.css dist/native-modules",
"prebuild:system": "cross-env rimraf dist/system",
"build:system": "cross-env tsc --project tsconfig.build.json --outDir dist/system --module system",
"postbuild:system": "cross-env copyfiles --up 1 src/**/*.html src/**/*.css dist/system",
"prebuild": "cross-env rimraf dist",
"build": "concurrently \"npm run build:amd\" \"npm run build:commonjs\" \"npm run build:es2015\" \"npm run build:native-modules\" \"npm run build:system\"",
"postbuild": "npm run build:doc",
"prebuild:doc": "cross-env rimraf doc/api.json && rimraf dist/doc-temp && tsc --project tsconfig.build.json --outFile dist/doc-temp/index.js && node doc/shape-defs",
"build:doc": "cross-env typedoc --json doc/api.json --excludeExternals --includeDeclarations --mode modules --target ES6 --name aurelia-ux-docs --ignoreCompilerErrors --tsconfig doc/tsconfig.json dist/doc-temp/",
"postbuild:doc": "cross-env node doc/shape-doc && rimraf dist/doc-temp"
},
"jspm": {
"registry": "npm",
"jspmPackage": true,
"main": "aurelia-validation",
"format": "amd",
"directories": {
"dist": "dist/amd"
},
"peerDependencies": {
"aurelia-binding": "^1.0.4",
"aurelia-dependency-injection": "^1.0.0",
"aurelia-logging": "^1.0.0",
"aurelia-pal": "^1.0.0",
"aurelia-task-queue": "^1.0.0",
"aurelia-templating": "^1.1.0"
},
"dependencies": {
"aurelia-binding": "^1.0.4",
"aurelia-dependency-injection": "^1.0.0",
"aurelia-logging": "^1.0.0",
"aurelia-pal": "^1.0.0",
"aurelia-task-queue": "^1.0.0",
"aurelia-templating": "^1.1.0"
},
"devDependencies": {}
},
"dependencies": {
"aurelia-binding": "^1.1.0",
"aurelia-dependency-injection": "^1.2.1",
"aurelia-logging": "^1.2.0",
"aurelia-pal": "^1.2.0",
"aurelia-pal-browser": "^1.1.0",
"aurelia-task-queue": "^1.0.0",
"aurelia-templating": "^1.1.4",
"aurelia-testing": "^1.0.0-beta.2.0.1",
"copyfiles": "^1.0.0",
"karma-jasmine": "^1.1.0",
"requirejs-text": "^2.0.15",
"typescript": "^2.2.0-dev.20161219"
},
"devDependencies": {
"aurelia-bootstrapper": "^1.0.0",
"aurelia-pal-browser": "^1.0.0",
"aurelia-polyfills": "^1.1.0",
"aurelia-testing": "^1.0.0-beta.2.0.0",
"concurrently": "^2.2.0",
"copyfiles": "^1.0.0",
"cross-env": "^2.0.1",
"jasmine-core": "^2.4.1",
"karma": "^1.2.0",
"karma-chrome-launcher": "^2.0.0",
"karma-ie-launcher": "^1.0.0",
"karma-jasmine": "^1.0.2",
"karma-requirejs": "^1.0.0",
"requirejs": "^2.2.0",
"requirejs-text": "^2.0.12",
"rimraf": "^2.5.4",
"tslint": "^3.15.1",
"typedoc": "^0.5.0",
"typescript": "next"
},
"aurelia": {
"build": {
"resources": [
"validate-binding-behavior",
"validation-errors-custom-attribute",
"validation-renderer-custom-attribute"
]
},
"documentation": {
"articles": [
{
"title": "Validation: Basics",
"href": "doc/article/en-US/validation-basics.md"
}
]
}
}
}
16 changes: 13 additions & 3 deletions src/implementation/standard-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class StandardValidator extends Validator {
* 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
* @param rules Optional. If unspecified, the rules will be looked up using the metadata
* for the object created by ValidationRules....on(class/object)
*/
public validateProperty(object: any, propertyName: string, rules?: any): Promise<ValidateResult[]> {
Expand All @@ -38,7 +38,7 @@ export class StandardValidator extends Validator {
/**
* 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
* @param rules Optional. If unspecified, the rules will be looked up using the metadata
* for the object created by ValidationRules....on(class/object)
*/
public validateObject(object: any, rules?: any): Promise<ValidateResult[]> {
Expand Down Expand Up @@ -108,7 +108,17 @@ export class StandardValidator extends Validator {
}

// validate.
const value = rule.property.name === null ? object : object[rule.property.name];
let value = rule.property.name === null ? object : object[rule.property.name];
console.log("standard-validator.ts 109 Property ", 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
let parts = rule.property.name.split('.');
value = object[ parts[ parts.length - 1 ]];
}
console.log("standard-validator.ts 118 Property ", rule.property.name);
let promiseOrBoolean = rule.condition(value, object);
if (!(promiseOrBoolean instanceof Promise)) {
promiseOrBoolean = Promise.resolve(promiseOrBoolean);
Expand Down
21 changes: 15 additions & 6 deletions src/implementation/validation-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,19 @@ export class ValidationParser {
return { name: <string>property, displayName: null };
}
const accessor = this.getAccessorExpression(property.toString());
if (accessor instanceof AccessScope
|| accessor instanceof AccessMember && accessor.object instanceof AccessScope) {
return {
name: accessor.name,
const isSubProp = accessor instanceof AccessMember && accessor.object instanceof AccessScope;
if (accessor instanceof AccessScope || isSubProp) {
let propName = (<any>accessor).name;
if (isSubProp) {
// iterate up the chain until we are in the 1st sub-object of the root object.
let ao = (<any>accessor).object;
while (ao) {
propName = ao.name + '.' + propName;
ao = ao.object;
}
}
return {
name: propName,
displayName: null
};
}
Expand All @@ -88,8 +97,8 @@ export class ValidationParser {
}

private getAccessorExpression(fn: string): Expression {
const classic = /^function\s*\([$_\w\d]+\)\s*\{\s*(?:"use strict";)?\s*return\s+[$_\w\d]+\.([$_\w\d]+)\s*;?\s*\}$/;
const arrow = /^\(?[$_\w\d]+\)?\s*=>\s*[$_\w\d]+\.([$_\w\d]+)$/;
const classic = /^function\s*\([$_\w\d]+\)\s*\{\s*(?:"use strict";)?\s*.*return\s+[$_\w\d]+\.([$_\w\d]+(\.[$_\w\d]+)*)\s*;?\s*\}$/;
const arrow = /^\(?[$_\w\d]+\)?\s*=>\s*(?:\{?.*return\s+)?[$_\w\d]+\.([$_\w\d]+(\.[$_\w\d]+)*);?\s*\}?$/;
const match = classic.exec(fn) || arrow.exec(fn);
if (match === null) {
throw new Error(`Unable to parse accessor function:\n${fn}`);
Expand Down
10 changes: 10 additions & 0 deletions src/property-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,22 @@ export function getPropertyInfo(expression: Expression, source: any): { object:

let object: null | undefined | Object;
let propertyName: string;
let ruleSrc = null;
if (expression instanceof AccessScope) {
object = source.bindingContext;
propertyName = expression.name;
} else if (expression instanceof AccessMember) {
object = getObject(originalExpression, expression.object, source);
propertyName = expression.name;
if (expression.object) {
// build the path to the property from the object root.
let exp: any = expression.object;
while (exp.object) {
propertyName = exp.name + '.' + propertyName;
exp = exp.object;
}
ruleSrc = <any>getObject(originalExpression, exp, source);
}
} else if (expression instanceof AccessKeyed) {
object = getObject(originalExpression, expression.object, source);
propertyName = expression.key.evaluate(source);
Expand Down
Loading

0 comments on commit 8dbc4a0

Please sign in to comment.