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
This commit is contained in:
Alec Wiseman 2015-11-20 01:36:28 -05:00
parent f6a8d04c32
commit 38cb526f60
7 changed files with 81 additions and 4 deletions

View File

@ -37,6 +37,7 @@ export {
RequiredValidator, RequiredValidator,
MinLengthValidator, MinLengthValidator,
MaxLengthValidator, MaxLengthValidator,
PatternValidator,
Validator Validator
} from './forms/directives/validators'; } from './forms/directives/validators';
export {FormBuilder} from './forms/form_builder'; export {FormBuilder} from './forms/form_builder';

View File

@ -14,7 +14,12 @@ import {
SelectControlValueAccessor, SelectControlValueAccessor,
NgSelectOption NgSelectOption
} from './directives/select_control_value_accessor'; } 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 {NgControlName} from './directives/ng_control_name';
export {NgFormControl} from './directives/ng_form_control'; export {NgFormControl} from './directives/ng_form_control';
@ -34,7 +39,12 @@ export {
SelectControlValueAccessor, SelectControlValueAccessor,
NgSelectOption NgSelectOption
} from './directives/select_control_value_accessor'; } 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 {NgControl} from './directives/ng_control';
export {ControlValueAccessor} from './directives/control_value_accessor'; export {ControlValueAccessor} from './directives/control_value_accessor';
@ -73,5 +83,6 @@ export const FORM_DIRECTIVES: Type[] = CONST_EXPR([
RequiredValidator, RequiredValidator,
MinLengthValidator, MinLengthValidator,
MaxLengthValidator MaxLengthValidator,
PatternValidator
]); ]);

View File

@ -100,3 +100,32 @@ export class MaxLengthValidator implements Validator {
validate(c: Control): {[key: string]: any} { return this._validator(c); } 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
*
* ```
* <input [ngControl]="fullName" pattern="[a-zA-Z ]*">
* ```
*/
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); }
}

View File

@ -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. * No-op validator.
*/ */

View File

@ -66,12 +66,28 @@ export function main() {
it("should not error on valid strings", it("should not error on valid strings",
() => { expect(Validators.maxLength(2)(new Control("aa"))).toEqual(null); }); () => { 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"))) expect(Validators.maxLength(2)(new Control("aaa")))
.toEqual({"maxlength": {"requiredLength": 2, "actualLength": 3}}); .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", () => { describe("compose", () => {
it("should return null when given null", it("should return null when given null",
() => { expect(Validators.compose(null)).toBe(null); }); () => { expect(Validators.compose(null)).toBe(null); });

View File

@ -427,6 +427,8 @@ var NG_COMMON = [
'ObservableListDiffFactory.create():dart', 'ObservableListDiffFactory.create():dart',
'ObservableListDiffFactory.supports():dart', 'ObservableListDiffFactory.supports():dart',
'ObservableListDiffFactory:dart', 'ObservableListDiffFactory:dart',
'PatternValidator',
'PatternValidator.validate()',
'PercentPipe', 'PercentPipe',
'PercentPipe.transform()', 'PercentPipe.transform()',
'RequiredValidator', 'RequiredValidator',
@ -452,6 +454,7 @@ var NG_COMMON = [
'Validators#maxLength()', 'Validators#maxLength()',
'Validators#minLength()', 'Validators#minLength()',
'Validators#nullValidator()', 'Validators#nullValidator()',
'Validators#pattern()',
'Validators#required()', 'Validators#required()',
'RadioButtonState', 'RadioButtonState',
'RadioButtonState.checked', 'RadioButtonState.checked',

View File

@ -772,6 +772,9 @@ const COMMON = [
'NgSwitchWhen.constructor(viewContainer:ViewContainerRef, templateRef:TemplateRef, ngSwitch:NgSwitch)', 'NgSwitchWhen.constructor(viewContainer:ViewContainerRef, templateRef:TemplateRef, ngSwitch:NgSwitch)',
'NgSwitchWhen.ngSwitchWhen=(value:any)', 'NgSwitchWhen.ngSwitchWhen=(value:any)',
'NumberPipe', 'NumberPipe',
'PatternValidator',
'PatternValidator.constructor(pattern:string)',
'PatternValidator.validate(c:Control):{[key:string]:any}',
'PercentPipe', 'PercentPipe',
'PercentPipe.transform(value:any, args:any[]):string', 'PercentPipe.transform(value:any, args:any[]):string',
'RequiredValidator', 'RequiredValidator',
@ -795,6 +798,7 @@ const COMMON = [
'Validators.maxLength(maxLength:number):Function', 'Validators.maxLength(maxLength:number):Function',
'Validators.minLength(minLength:number):Function', 'Validators.minLength(minLength:number):Function',
'Validators.nullValidator(c:any):{[key:string]:boolean}', 'Validators.nullValidator(c:any):{[key:string]:boolean}',
'Validators.pattern(pattern:string):Function',
'Validators.required(control:Control):{[key:string]:boolean}', 'Validators.required(control:Control):{[key:string]:boolean}',
'RadioButtonState', 'RadioButtonState',
'RadioButtonState.constructor(checked:boolean, value:string)', 'RadioButtonState.constructor(checked:boolean, value:string)',