parent
2337469753
commit
e725542703
|
@ -31,7 +31,7 @@ export {
|
|||
NgSelectOption,
|
||||
SelectControlValueAccessor
|
||||
} from './forms/directives/select_control_value_accessor';
|
||||
export {FORM_DIRECTIVES} from './forms/directives';
|
||||
export {FORM_DIRECTIVES, RadioButtonState} from './forms/directives';
|
||||
export {NG_VALIDATORS, NG_ASYNC_VALIDATORS, Validators} from './forms/validators';
|
||||
export {
|
||||
RequiredValidator,
|
||||
|
@ -39,4 +39,25 @@ export {
|
|||
MaxLengthValidator,
|
||||
Validator
|
||||
} from './forms/directives/validators';
|
||||
export {FormBuilder, FORM_PROVIDERS, FORM_BINDINGS} from './forms/form_builder';
|
||||
export {FormBuilder} from './forms/form_builder';
|
||||
import {FormBuilder} from './forms/form_builder';
|
||||
import {RadioControlRegistry} from './forms/directives/radio_control_value_accessor';
|
||||
import {Type, CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* Shorthand set of providers used for building Angular forms.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* bootstrap(MyApp, [FORM_PROVIDERS]);
|
||||
* ```
|
||||
*/
|
||||
export const FORM_PROVIDERS: Type[] = CONST_EXPR([FormBuilder, RadioControlRegistry]);
|
||||
|
||||
/**
|
||||
* See {@link FORM_PROVIDERS} instead.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const FORM_BINDINGS = FORM_PROVIDERS;
|
||||
|
|
|
@ -8,6 +8,7 @@ import {NgForm} from './directives/ng_form';
|
|||
import {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
import {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
import {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
import {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
import {NgControlStatus} from './directives/ng_control_status';
|
||||
import {
|
||||
SelectControlValueAccessor,
|
||||
|
@ -23,6 +24,10 @@ export {NgFormModel} from './directives/ng_form_model';
|
|||
export {NgForm} from './directives/ng_form';
|
||||
export {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
export {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
export {
|
||||
RadioControlValueAccessor,
|
||||
RadioButtonState
|
||||
} from './directives/radio_control_value_accessor';
|
||||
export {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
export {NgControlStatus} from './directives/ng_control_status';
|
||||
export {
|
||||
|
@ -63,6 +68,7 @@ export const FORM_DIRECTIVES: Type[] = CONST_EXPR([
|
|||
NumberValueAccessor,
|
||||
CheckboxControlValueAccessor,
|
||||
SelectControlValueAccessor,
|
||||
RadioControlValueAccessor,
|
||||
NgControlStatus,
|
||||
|
||||
RequiredValidator,
|
||||
|
|
|
@ -18,7 +18,7 @@ const CHECKBOX_VALUE_ACCESSOR = CONST_EXPR(new Provider(
|
|||
selector:
|
||||
'input[type=checkbox][ngControl],input[type=checkbox][ngFormControl],input[type=checkbox][ngModel]',
|
||||
host: {'(change)': 'onChange($event.target.checked)', '(blur)': 'onTouched()'},
|
||||
bindings: [CHECKBOX_VALUE_ACCESSOR]
|
||||
providers: [CHECKBOX_VALUE_ACCESSOR]
|
||||
})
|
||||
export class CheckboxControlValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_) => {};
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
import {
|
||||
Directive,
|
||||
ElementRef,
|
||||
Renderer,
|
||||
Self,
|
||||
forwardRef,
|
||||
Provider,
|
||||
Attribute,
|
||||
Input,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Injector,
|
||||
Injectable
|
||||
} from 'angular2/core';
|
||||
import {
|
||||
NG_VALUE_ACCESSOR,
|
||||
ControlValueAccessor
|
||||
} from 'angular2/src/common/forms/directives/control_value_accessor';
|
||||
import {NgControl} from 'angular2/src/common/forms/directives/ng_control';
|
||||
import {CONST_EXPR, looseIdentical, isPresent} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
const RADIO_VALUE_ACCESSOR = CONST_EXPR(new Provider(
|
||||
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => RadioControlValueAccessor), multi: true}));
|
||||
|
||||
|
||||
/**
|
||||
* Internal class used by Angular to uncheck radio buttons with the matching name.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RadioControlRegistry {
|
||||
private _accessors: any[] = [];
|
||||
|
||||
add(control: NgControl, accessor: RadioControlValueAccessor) {
|
||||
this._accessors.push([control, accessor]);
|
||||
}
|
||||
|
||||
remove(accessor: RadioControlValueAccessor) {
|
||||
var indexToRemove = -1;
|
||||
for (var i = 0; i < this._accessors.length; ++i) {
|
||||
if (this._accessors[i][1] === accessor) {
|
||||
indexToRemove = i;
|
||||
}
|
||||
}
|
||||
ListWrapper.removeAt(this._accessors, indexToRemove);
|
||||
}
|
||||
|
||||
select(accessor: RadioControlValueAccessor) {
|
||||
this._accessors.forEach((c) => {
|
||||
if (c[0].control.root === accessor._control.control.root && c[1] !== accessor) {
|
||||
c[1].fireUncheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The value provided by the forms API for radio buttons.
|
||||
*/
|
||||
export class RadioButtonState {
|
||||
constructor(public checked: boolean, public value: string) {}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The accessor for writing a radio control value and listening to changes that is used by the
|
||||
* {@link NgModel}, {@link NgFormControl}, and {@link NgControlName} directives.
|
||||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* @Component({
|
||||
* template: `
|
||||
* <input type="radio" name="food" [(ngModel)]="foodChicken">
|
||||
* <input type="radio" name="food" [(ngModel)]="foodFish">
|
||||
* `
|
||||
* })
|
||||
* class FoodCmp {
|
||||
* foodChicken = new RadioButtonState(true, "chicken");
|
||||
* foodFish = new RadioButtonState(false, "fish");
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'input[type=radio][ngControl],input[type=radio][ngFormControl],input[type=radio][ngModel]',
|
||||
host: {'(change)': 'onChange()', '(blur)': 'onTouched()'},
|
||||
providers: [RADIO_VALUE_ACCESSOR]
|
||||
})
|
||||
export class RadioControlValueAccessor implements ControlValueAccessor,
|
||||
OnDestroy, OnInit {
|
||||
_state: RadioButtonState;
|
||||
_control: NgControl;
|
||||
@Input() name: string;
|
||||
_fn: Function;
|
||||
onChange = () => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef,
|
||||
private _registry: RadioControlRegistry, private _injector: Injector) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._control = this._injector.get(NgControl);
|
||||
this._registry.add(this._control, this);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this._registry.remove(this); }
|
||||
|
||||
writeValue(value: any): void {
|
||||
this._state = value;
|
||||
if (isPresent(value) && value.checked) {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'checked', true);
|
||||
}
|
||||
}
|
||||
|
||||
registerOnChange(fn: (_: any) => {}): void {
|
||||
this._fn = fn;
|
||||
this.onChange = () => {
|
||||
fn(new RadioButtonState(true, this._state.value));
|
||||
this._registry.select(this);
|
||||
};
|
||||
}
|
||||
|
||||
fireUncheck(): void { this._fn(new RadioButtonState(false, this._state.value)); }
|
||||
|
||||
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
|
||||
}
|
|
@ -105,22 +105,4 @@ export class FormBuilder {
|
|||
return this.control(controlConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand set of providers used for building Angular forms.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* bootstrap(MyApp, [FORM_PROVIDERS]);
|
||||
* ```
|
||||
*/
|
||||
export const FORM_PROVIDERS: Type[] = CONST_EXPR([FormBuilder]);
|
||||
|
||||
/**
|
||||
* See {@link FORM_PROVIDERS} instead.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const FORM_BINDINGS = FORM_PROVIDERS;
|
||||
}
|
|
@ -208,6 +208,16 @@ export abstract class AbstractControl {
|
|||
return isPresent(this.getError(errorCode, path));
|
||||
}
|
||||
|
||||
get root(): AbstractControl {
|
||||
let x: AbstractControl = this;
|
||||
|
||||
while (isPresent(x._parent)) {
|
||||
x = x._parent;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateControlsErrors(): void {
|
||||
this._status = this._calculateStatus();
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
dispatchEvent,
|
||||
fakeAsync,
|
||||
tick,
|
||||
flushMicrotasks,
|
||||
expect,
|
||||
it,
|
||||
inject,
|
||||
|
@ -31,7 +32,8 @@ import {
|
|||
NgFor,
|
||||
NgForm,
|
||||
Validators,
|
||||
Validator
|
||||
Validator,
|
||||
RadioButtonState
|
||||
} from 'angular2/common';
|
||||
import {Provider, forwardRef, Input} from 'angular2/core';
|
||||
import {By} from 'angular2/platform/browser';
|
||||
|
@ -328,6 +330,33 @@ export function main() {
|
|||
});
|
||||
}));
|
||||
|
||||
it("should support <type=radio>",
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var t = `<form [ngFormModel]="form">
|
||||
<input type="radio" ngControl="foodChicken" name="food">
|
||||
<input type="radio" ngControl="foodFish" name="food">
|
||||
</form>`;
|
||||
|
||||
tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => {
|
||||
fixture.debugElement.componentInstance.form = new ControlGroup({
|
||||
"foodChicken": new Control(new RadioButtonState(false, 'chicken')),
|
||||
"foodFish": new Control(new RadioButtonState(true, 'fish'))
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
var input = fixture.debugElement.query(By.css("input"));
|
||||
expect(input.nativeElement.checked).toEqual(false);
|
||||
|
||||
dispatchEvent(input.nativeElement, "change");
|
||||
fixture.detectChanges();
|
||||
|
||||
let value = fixture.debugElement.componentInstance.form.value;
|
||||
expect(value['foodChicken'].checked).toEqual(true);
|
||||
expect(value['foodFish'].checked).toEqual(false);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should support <select>",
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
var t = `<div [ngFormModel]="form">
|
||||
|
@ -812,9 +841,50 @@ export function main() {
|
|||
|
||||
expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue");
|
||||
})));
|
||||
});
|
||||
|
||||
|
||||
it("should support <type=radio>",
|
||||
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
|
||||
var t = `<form>
|
||||
<input type="radio" name="food" ngControl="chicken" [(ngModel)]="data['chicken1']">
|
||||
<input type="radio" name="food" ngControl="fish" [(ngModel)]="data['fish1']">
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<input type="radio" name="food" ngControl="chicken" [(ngModel)]="data['chicken2']">
|
||||
<input type="radio" name="food" ngControl="fish" [(ngModel)]="data['fish2']">
|
||||
</form>`;
|
||||
|
||||
var fixture: ComponentFixture;
|
||||
tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((f) => { fixture = f; });
|
||||
tick();
|
||||
|
||||
fixture.debugElement.componentInstance.data = {
|
||||
'chicken1': new RadioButtonState(false, 'chicken'),
|
||||
'fish1': new RadioButtonState(true, 'fish'),
|
||||
|
||||
'chicken2': new RadioButtonState(false, 'chicken'),
|
||||
'fish2': new RadioButtonState(true, 'fish')
|
||||
};
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
var input = fixture.debugElement.query(By.css("input"));
|
||||
expect(input.nativeElement.checked).toEqual(false);
|
||||
|
||||
dispatchEvent(input.nativeElement, "change");
|
||||
tick();
|
||||
|
||||
let data = fixture.debugElement.componentInstance.data;
|
||||
|
||||
expect(data['chicken1']).toEqual(new RadioButtonState(true, 'chicken'));
|
||||
expect(data['fish1']).toEqual(new RadioButtonState(false, 'fish'));
|
||||
|
||||
expect(data['chicken2']).toEqual(new RadioButtonState(false, 'chicken'));
|
||||
expect(data['fish2']).toEqual(new RadioButtonState(true, 'fish'));
|
||||
})));
|
||||
});
|
||||
|
||||
describe("setting status classes", () => {
|
||||
it("should work with single fields",
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
|
|
|
@ -51,6 +51,7 @@ var NG_COMMON = [
|
|||
'AbstractControl.validator',
|
||||
'AbstractControl.validator=',
|
||||
'AbstractControl.value',
|
||||
'AbstractControl.root',
|
||||
'AbstractControl.valueChanges',
|
||||
'AbstractControlDirective',
|
||||
'AbstractControlDirective.control',
|
||||
|
@ -102,6 +103,7 @@ var NG_COMMON = [
|
|||
'Control.validator',
|
||||
'Control.validator=',
|
||||
'Control.value',
|
||||
'Control.root',
|
||||
'Control.valueChanges',
|
||||
'ControlArray',
|
||||
'ControlArray.asyncValidator',
|
||||
|
@ -134,6 +136,7 @@ var NG_COMMON = [
|
|||
'ControlArray.validator',
|
||||
'ControlArray.validator=',
|
||||
'ControlArray.value',
|
||||
'ControlArray.root',
|
||||
'ControlArray.valueChanges',
|
||||
'ControlContainer',
|
||||
'ControlContainer.control',
|
||||
|
@ -179,6 +182,7 @@ var NG_COMMON = [
|
|||
'ControlGroup.validator',
|
||||
'ControlGroup.validator=',
|
||||
'ControlGroup.value',
|
||||
'ControlGroup.root',
|
||||
'ControlGroup.valueChanges',
|
||||
'ControlValueAccessor:dart',
|
||||
'CurrencyPipe',
|
||||
|
@ -447,7 +451,12 @@ var NG_COMMON = [
|
|||
'Validators#maxLength()',
|
||||
'Validators#minLength()',
|
||||
'Validators#nullValidator()',
|
||||
'Validators#required()'
|
||||
'Validators#required()',
|
||||
'RadioButtonState',
|
||||
'RadioButtonState.checked',
|
||||
'RadioButtonState.checked=',
|
||||
'RadioButtonState.value',
|
||||
'RadioButtonState.value='
|
||||
];
|
||||
|
||||
var NG_COMPILER = [
|
||||
|
|
|
@ -566,6 +566,7 @@ const COMMON = [
|
|||
'AbstractControl.updateValueAndValidity({onlySelf,emitEvent}:{onlySelf?:boolean, emitEvent?:boolean}):void',
|
||||
'AbstractControl.valid:boolean',
|
||||
'AbstractControl.value:any',
|
||||
'AbstractControl.root:AbstractControl',
|
||||
'AbstractControl.valueChanges:Observable<any>',
|
||||
'AbstractControlDirective',
|
||||
'AbstractControlDirective.control:AbstractControl',
|
||||
|
@ -794,6 +795,8 @@ const COMMON = [
|
|||
'Validators.minLength(minLength:number):Function',
|
||||
'Validators.nullValidator(c:any):{[key:string]:boolean}',
|
||||
'Validators.required(control:Control):{[key:string]:boolean}',
|
||||
'RadioButtonState',
|
||||
'RadioButtonState.constructor(checked:boolean, value:string)',
|
||||
'const COMMON_DIRECTIVES:Type[][]',
|
||||
'const COMMON_PIPES:any',
|
||||
'const CORE_DIRECTIVES:Type[]',
|
||||
|
|
Loading…
Reference in New Issue