feat(forms): add minlength and maxlength validators

Closes #4705
This commit is contained in:
vsavkin 2015-10-13 14:38:13 -07:00 committed by Victor Savkin
parent 50e922f37b
commit e82a35d1fd
8 changed files with 141 additions and 17 deletions

View File

@ -34,7 +34,11 @@ export {
} from './forms/directives/select_control_value_accessor'; } from './forms/directives/select_control_value_accessor';
export {FORM_DIRECTIVES} from './forms/directives'; export {FORM_DIRECTIVES} from './forms/directives';
export {NG_VALIDATORS, Validators} from './forms/validators'; export {NG_VALIDATORS, Validators} from './forms/validators';
export {DefaultValidators} from './forms/directives/validators'; export {
RequiredValidator,
MinLengthValidator,
MaxLengthValidator
} from './forms/directives/validators';
export {FormBuilder} from './forms/form_builder'; export {FormBuilder} from './forms/form_builder';
import {FormBuilder} from './forms/form_builder'; import {FormBuilder} from './forms/form_builder';

View File

@ -12,7 +12,7 @@ import {
SelectControlValueAccessor, SelectControlValueAccessor,
NgSelectOption NgSelectOption
} from './directives/select_control_value_accessor'; } from './directives/select_control_value_accessor';
import {DefaultValidators} from './directives/validators'; import {RequiredValidator, MinLengthValidator, MaxLengthValidator} 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';
@ -28,7 +28,7 @@ export {
SelectControlValueAccessor, SelectControlValueAccessor,
NgSelectOption NgSelectOption
} from './directives/select_control_value_accessor'; } from './directives/select_control_value_accessor';
export {DefaultValidators} from './directives/validators'; export {RequiredValidator, MinLengthValidator, MaxLengthValidator} from './directives/validators';
export {NgControlStatus} from './directives/ng_control_status'; export {NgControlStatus} from './directives/ng_control_status';
/** /**
@ -62,5 +62,7 @@ export const FORM_DIRECTIVES: Type[] = CONST_EXPR([
SelectControlValueAccessor, SelectControlValueAccessor,
NgControlStatus, NgControlStatus,
DefaultValidators RequiredValidator,
MinLengthValidator,
MaxLengthValidator
]); ]);

View File

@ -1,14 +1,53 @@
import {forwardRef, Provider, OpaqueToken} from 'angular2/src/core/di'; import {forwardRef, Provider, OpaqueToken} from 'angular2/src/core/di';
import {CONST_EXPR} from 'angular2/src/core/facade/lang'; import {CONST_EXPR} from 'angular2/src/core/facade/lang';
import {Directive} from 'angular2/src/core/metadata'; import {Attribute, Directive} from 'angular2/src/core/metadata';
import {Validators, NG_VALIDATORS} from '../validators'; import {Validators, NG_VALIDATORS} from '../validators';
import {NumberWrapper} from "angular2/src/core/facade/lang";
const DEFAULT_VALIDATORS = const REQUIRED_VALIDATOR =
CONST_EXPR(new Provider(NG_VALIDATORS, {useValue: Validators.required, multi: true})); CONST_EXPR(new Provider(NG_VALIDATORS, {useValue: Validators.required, multi: true}));
@Directive({ @Directive({
selector: '[required][ng-control],[required][ng-form-control],[required][ng-model]', selector: '[required][ng-control],[required][ng-form-control],[required][ng-model]',
bindings: [DEFAULT_VALIDATORS] providers: [REQUIRED_VALIDATOR]
}) })
export class DefaultValidators { export class RequiredValidator {
} }
function createMinLengthValidator(dir): any {
return Validators.minLength(dir.minLength);
}
const MIN_LENGTH_VALIDATOR = CONST_EXPR(new Provider(NG_VALIDATORS, {
useFactory: createMinLengthValidator,
deps: [forwardRef(() => MinLengthValidator)],
multi: true
}));
@Directive({
selector: '[minlength][ng-control],[minlength][ng-form-control],[minlength][ng-model]',
providers: [MIN_LENGTH_VALIDATOR]
})
export class MinLengthValidator {
minLength: number;
constructor(@Attribute("minlength") minLength: string) {
this.minLength = NumberWrapper.parseInt(minLength, 10);
}
}
function createMaxLengthValidator(dir): any {
return Validators.maxLength(dir.maxLength);
}
const MAX_LENGTH_VALIDATOR = CONST_EXPR(new Provider(NG_VALIDATORS, {
useFactory: createMaxLengthValidator,
deps: [forwardRef(() => MaxLengthValidator)],
multi: true
}));
@Directive({
selector: '[maxlength][ng-control],[maxlength][ng-form-control],[maxlength][ng-model]',
providers: [MAX_LENGTH_VALIDATOR]
})
export class MaxLengthValidator {
maxLength: number;
constructor(@Attribute("maxlength") maxLength: string) {
this.maxLength = NumberWrapper.parseInt(maxLength, 10);
}
}

View File

@ -21,6 +21,26 @@ export class Validators {
return isBlank(control.value) || control.value == "" ? {"required": true} : null; return isBlank(control.value) || control.value == "" ? {"required": true} : null;
} }
static minLength(minLength: number): Function {
return (control: modelModule.Control): {[key: string]: any} => {
if (isPresent(Validators.required(control))) return null;
var v: string = control.value;
return v.length < minLength ?
{"minlength": {"requiredLength": minLength, "actualLength": v.length}} :
null;
};
}
static maxLength(maxLength: number): Function {
return (control: modelModule.Control): {[key: string]: any} => {
if (isPresent(Validators.required(control))) return null;
var v: string = control.value;
return v.length > maxLength ?
{"maxlength": {"requiredLength": maxLength, "actualLength": v.length}} :
null;
};
}
static nullValidator(c: any): {[key: string]: boolean} { return null; } static nullValidator(c: any): {[key: string]: boolean} { return null; }
static compose(validators: Function[]): Function { static compose(validators: Function[]): Function {

View File

@ -28,7 +28,6 @@ import {
NgForm, NgForm,
NgModel, NgModel,
NgFormControl, NgFormControl,
DefaultValidators,
NgControl, NgControl,
DefaultValueAccessor, DefaultValueAccessor,
CheckboxControlValueAccessor, CheckboxControlValueAccessor,

View File

@ -349,23 +349,43 @@ export function main() {
describe("validations", () => { describe("validations", () => {
it("should use validators defined in html", it("should use validators defined in html",
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var form = new ControlGroup({"login": new Control("aa")}); var form = new ControlGroup(
{"login": new Control(""), "min": new Control(""), "max": new Control("")});
var t = `<div [ng-form-model]="form"> var t = `<div [ng-form-model]="form">
<input type="text" ng-control="login" required> <input type="text" ng-control="login" required>
<input type="text" ng-control="min" minlength="3">
<input type="text" ng-control="max" maxlength="3">
</div>`; </div>`;
tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((rootTC) => { tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((rootTC) => {
rootTC.debugElement.componentInstance.form = form; rootTC.debugElement.componentInstance.form = form;
rootTC.detectChanges(); rootTC.detectChanges();
var required = rootTC.debugElement.query(By.css("[required]"));
var minLength = rootTC.debugElement.query(By.css("[minlength]"));
var maxLength = rootTC.debugElement.query(By.css("[maxlength]"));
required.nativeElement.value = "";
minLength.nativeElement.value = "1";
maxLength.nativeElement.value = "1234";
dispatchEvent(required.nativeElement, "change");
dispatchEvent(minLength.nativeElement, "change");
dispatchEvent(maxLength.nativeElement, "change");
expect(form.hasError("required", ["login"])).toEqual(true);
expect(form.hasError("minlength", ["min"])).toEqual(true);
expect(form.hasError("maxlength", ["max"])).toEqual(true);
required.nativeElement.value = "1";
minLength.nativeElement.value = "123";
maxLength.nativeElement.value = "123";
dispatchEvent(required.nativeElement, "change");
dispatchEvent(minLength.nativeElement, "change");
dispatchEvent(maxLength.nativeElement, "change");
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
var input = rootTC.debugElement.query(By.css("input"));
input.nativeElement.value = "";
dispatchEvent(input.nativeElement, "change");
expect(form.valid).toEqual(false);
async.done(); async.done();
}); });
})); }));

View File

@ -32,6 +32,38 @@ export function main() {
() => { expect(Validators.required(new Control("not empty"))).toEqual(null); }); () => { expect(Validators.required(new Control("not empty"))).toEqual(null); });
}); });
describe("minLength", () => {
it("should not error on an empty string",
() => { expect(Validators.minLength(2)(new Control(""))).toEqual(null); });
it("should not error on null",
() => { expect(Validators.minLength(2)(new Control(null))).toEqual(null); });
it("should not error on valid strings",
() => { expect(Validators.minLength(2)(new Control("aa"))).toEqual(null); });
it("should error on short strings", () => {
expect(Validators.minLength(2)(new Control("a")))
.toEqual({"minlength": {"requiredLength": 2, "actualLength": 1}});
});
});
describe("maxLength", () => {
it("should not error on an empty string",
() => { expect(Validators.maxLength(2)(new Control(""))).toEqual(null); });
it("should not error on null",
() => { expect(Validators.maxLength(2)(new Control(null))).toEqual(null); });
it("should not error on valid strings",
() => { expect(Validators.maxLength(2)(new Control("aa"))).toEqual(null); });
it("should error on short strings", () => {
expect(Validators.maxLength(2)(new Control("aaa")))
.toEqual({"maxlength": {"requiredLength": 2, "actualLength": 3}});
});
});
describe("compose", () => { describe("compose", () => {
it("should return a null validator when given null", it("should return a null validator when given null",
() => { expect(Validators.compose(null)).toBe(Validators.nullValidator); }); () => { expect(Validators.compose(null)).toBe(Validators.nullValidator); });

View File

@ -396,7 +396,13 @@ var NG_API = [
'DebugElement.queryAll()', 'DebugElement.queryAll()',
'DecimalPipe', 'DecimalPipe',
'DecimalPipe.transform()', 'DecimalPipe.transform()',
'DefaultValidators', 'RequiredValidator',
'MinLengthValidator',
'MinLengthValidator.minLength',
'MinLengthValidator.minLength=',
'MaxLengthValidator',
'MaxLengthValidator.maxLength',
'MaxLengthValidator.maxLength=',
'DefaultValueAccessor', 'DefaultValueAccessor',
'DefaultValueAccessor.onChange', 'DefaultValueAccessor.onChange',
'DefaultValueAccessor.onChange=', 'DefaultValueAccessor.onChange=',
@ -995,6 +1001,8 @@ var NG_API = [
'Validators#group()', 'Validators#group()',
'Validators#nullValidator()', 'Validators#nullValidator()',
'Validators#required()', 'Validators#required()',
'Validators#minLength()',
'Validators#maxLength()',
'Validators', 'Validators',
'View', 'View',
'View.directives', 'View.directives',