From 38cb526f60f910ddbdb532639a03b57d9da378c5 Mon Sep 17 00:00:00 2001 From: Alec Wiseman Date: Fri, 20 Nov 2015 01:36:28 -0500 Subject: [PATCH] feat(forms/validators): pattern validator Adding static pattern validation method to Validators Adding a directive for the pattern validator Applying clang-format rules to modified files Updating public api spec for new pattern validator Adding pattern validator to public api guard tool For #5411 Closes #5561 --- modules/angular2/src/common/forms.ts | 1 + .../angular2/src/common/forms/directives.ts | 17 +++++++++-- .../src/common/forms/directives/validators.ts | 29 +++++++++++++++++++ .../angular2/src/common/forms/validators.ts | 13 +++++++++ .../test/common/forms/validators_spec.ts | 18 +++++++++++- modules/angular2/test/public_api_spec.ts | 3 ++ tools/public_api_guard/public_api_spec.ts | 4 +++ 7 files changed, 81 insertions(+), 4 deletions(-) diff --git a/modules/angular2/src/common/forms.ts b/modules/angular2/src/common/forms.ts index 037ba6b721..f25b8067c2 100644 --- a/modules/angular2/src/common/forms.ts +++ b/modules/angular2/src/common/forms.ts @@ -37,6 +37,7 @@ export { RequiredValidator, MinLengthValidator, MaxLengthValidator, + PatternValidator, Validator } from './forms/directives/validators'; export {FormBuilder} from './forms/form_builder'; diff --git a/modules/angular2/src/common/forms/directives.ts b/modules/angular2/src/common/forms/directives.ts index 6bc968025b..7024aa3b80 100644 --- a/modules/angular2/src/common/forms/directives.ts +++ b/modules/angular2/src/common/forms/directives.ts @@ -14,7 +14,12 @@ import { SelectControlValueAccessor, NgSelectOption } from './directives/select_control_value_accessor'; -import {RequiredValidator, MinLengthValidator, MaxLengthValidator} from './directives/validators'; +import { + RequiredValidator, + MinLengthValidator, + MaxLengthValidator, + PatternValidator +} from './directives/validators'; export {NgControlName} from './directives/ng_control_name'; export {NgFormControl} from './directives/ng_form_control'; @@ -34,7 +39,12 @@ export { SelectControlValueAccessor, NgSelectOption } from './directives/select_control_value_accessor'; -export {RequiredValidator, MinLengthValidator, MaxLengthValidator} from './directives/validators'; +export { + RequiredValidator, + MinLengthValidator, + MaxLengthValidator, + PatternValidator +} from './directives/validators'; export {NgControl} from './directives/ng_control'; export {ControlValueAccessor} from './directives/control_value_accessor'; @@ -73,5 +83,6 @@ export const FORM_DIRECTIVES: Type[] = CONST_EXPR([ RequiredValidator, MinLengthValidator, - MaxLengthValidator + MaxLengthValidator, + PatternValidator ]); diff --git a/modules/angular2/src/common/forms/directives/validators.ts b/modules/angular2/src/common/forms/directives/validators.ts index dcb3189bcc..36b725cd81 100644 --- a/modules/angular2/src/common/forms/directives/validators.ts +++ b/modules/angular2/src/common/forms/directives/validators.ts @@ -100,3 +100,32 @@ export class MaxLengthValidator implements Validator { validate(c: Control): {[key: string]: any} { return this._validator(c); } } + + +/** + * A Directive that adds the `pattern` validator to any controls marked with the + * `pattern` attribute, via the {@link NG_VALIDATORS} binding. Uses attribute value + * as the regex to validate Control value against. Follows pattern attribute + * semantics; i.e. regex must match entire Control value. + * + * ### Example + * + * ``` + * + * ``` + */ +const PATTERN_VALIDATOR = CONST_EXPR( + new Provider(NG_VALIDATORS, {useExisting: forwardRef(() => PatternValidator), multi: true})); +@Directive({ + selector: '[pattern][ngControl],[pattern][ngFormControl],[pattern][ngModel]', + providers: [PATTERN_VALIDATOR] +}) +export class PatternValidator implements Validator { + private _validator: Function; + + constructor(@Attribute("pattern") pattern: string) { + this._validator = Validators.pattern(pattern); + } + + validate(c: Control): {[key: string]: any} { return this._validator(c); } +} diff --git a/modules/angular2/src/common/forms/validators.ts b/modules/angular2/src/common/forms/validators.ts index 3cc476ca71..7abe20b1b1 100644 --- a/modules/angular2/src/common/forms/validators.ts +++ b/modules/angular2/src/common/forms/validators.ts @@ -75,6 +75,19 @@ export class Validators { }; } + /** + * Validator that requires a control to match a regex to its value. + */ + static pattern(pattern: string): Function { + return (control: modelModule.Control): {[key: string]: any} => { + if (isPresent(Validators.required(control))) return null; + let regex = new RegExp(`^${pattern}$`); + let v: string = control.value; + return regex.test(v) ? null : + {"pattern": {"requiredPattern": `^${pattern}$`, "actualValue": v}}; + }; + } + /** * No-op validator. */ diff --git a/modules/angular2/test/common/forms/validators_spec.ts b/modules/angular2/test/common/forms/validators_spec.ts index 2ce72b87aa..54f6728a7a 100644 --- a/modules/angular2/test/common/forms/validators_spec.ts +++ b/modules/angular2/test/common/forms/validators_spec.ts @@ -66,12 +66,28 @@ export function main() { it("should not error on valid strings", () => { expect(Validators.maxLength(2)(new Control("aa"))).toEqual(null); }); - it("should error on short strings", () => { + it("should error on long strings", () => { expect(Validators.maxLength(2)(new Control("aaa"))) .toEqual({"maxlength": {"requiredLength": 2, "actualLength": 3}}); }); }); + describe("pattern", () => { + it("should not error on an empty string", + () => { expect(Validators.pattern("[a-zA-Z ]*")(new Control(""))).toEqual(null); }); + + it("should not error on null", + () => { expect(Validators.pattern("[a-zA-Z ]*")(new Control(null))).toEqual(null); }); + + it("should not error on valid strings", + () => { expect(Validators.pattern("[a-zA-Z ]*")(new Control("aaAA"))).toEqual(null); }); + + it("should error on failure to match string", () => { + expect(Validators.pattern("[a-zA-Z ]*")(new Control("aaa0"))) + .toEqual({"pattern": {"requiredPattern": "^[a-zA-Z ]*$", "actualValue": "aaa0"}}); + }); + }); + describe("compose", () => { it("should return null when given null", () => { expect(Validators.compose(null)).toBe(null); }); diff --git a/modules/angular2/test/public_api_spec.ts b/modules/angular2/test/public_api_spec.ts index 1a1e118939..0becc25025 100644 --- a/modules/angular2/test/public_api_spec.ts +++ b/modules/angular2/test/public_api_spec.ts @@ -427,6 +427,8 @@ var NG_COMMON = [ 'ObservableListDiffFactory.create():dart', 'ObservableListDiffFactory.supports():dart', 'ObservableListDiffFactory:dart', + 'PatternValidator', + 'PatternValidator.validate()', 'PercentPipe', 'PercentPipe.transform()', 'RequiredValidator', @@ -452,6 +454,7 @@ var NG_COMMON = [ 'Validators#maxLength()', 'Validators#minLength()', 'Validators#nullValidator()', + 'Validators#pattern()', 'Validators#required()', 'RadioButtonState', 'RadioButtonState.checked', diff --git a/tools/public_api_guard/public_api_spec.ts b/tools/public_api_guard/public_api_spec.ts index 191bc89af1..1b71680138 100644 --- a/tools/public_api_guard/public_api_spec.ts +++ b/tools/public_api_guard/public_api_spec.ts @@ -772,6 +772,9 @@ const COMMON = [ 'NgSwitchWhen.constructor(viewContainer:ViewContainerRef, templateRef:TemplateRef, ngSwitch:NgSwitch)', 'NgSwitchWhen.ngSwitchWhen=(value:any)', 'NumberPipe', + 'PatternValidator', + 'PatternValidator.constructor(pattern:string)', + 'PatternValidator.validate(c:Control):{[key:string]:any}', 'PercentPipe', 'PercentPipe.transform(value:any, args:any[]):string', 'RequiredValidator', @@ -795,6 +798,7 @@ const COMMON = [ 'Validators.maxLength(maxLength:number):Function', 'Validators.minLength(minLength:number):Function', 'Validators.nullValidator(c:any):{[key:string]:boolean}', + 'Validators.pattern(pattern:string):Function', 'Validators.required(control:Control):{[key:string]:boolean}', 'RadioButtonState', 'RadioButtonState.constructor(checked:boolean, value:string)',