fix(forms): fixed the handling of the select element

This commit is contained in:
vsavkin 2015-06-13 12:15:42 -07:00
parent 9bad70be5e
commit f1541e65b3
6 changed files with 90 additions and 15 deletions

View File

@ -7,7 +7,10 @@ import {NgFormModel} from './directives/ng_form_model';
import {NgForm} from './directives/ng_form'; import {NgForm} from './directives/ng_form';
import {DefaultValueAccessor} from './directives/default_value_accessor'; import {DefaultValueAccessor} from './directives/default_value_accessor';
import {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor'; import {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
import {SelectControlValueAccessor} from './directives/select_control_value_accessor'; import {
SelectControlValueAccessor,
NgSelectOption
} from './directives/select_control_value_accessor';
import {NgRequiredValidator} from './directives/validators'; import {NgRequiredValidator} from './directives/validators';
export {NgControlName} from './directives/ng_control_name'; export {NgControlName} from './directives/ng_control_name';
@ -40,6 +43,7 @@ export const formDirectives: List<Type> = CONST_EXPR([
NgFormModel, NgFormModel,
NgForm, NgForm,
NgSelectOption,
DefaultValueAccessor, DefaultValueAccessor,
CheckboxControlValueAccessor, CheckboxControlValueAccessor,
SelectControlValueAccessor, SelectControlValueAccessor,

View File

@ -1,6 +1,7 @@
import {Directive} from 'angular2/angular2'; import {Directive, Renderer, ElementRef} from 'angular2/angular2';
import {NgControl} from './ng_control'; import {NgControl} from './ng_control';
import {ControlValueAccessor} from './control_value_accessor'; import {ControlValueAccessor} from './control_value_accessor';
import {setProperty} from './shared';
/** /**
* The accessor for writing a value and listening to changes on a checkbox input element. * The accessor for writing a value and listening to changes on a checkbox input element.
@ -32,13 +33,18 @@ export class CheckboxControlValueAccessor implements ControlValueAccessor {
onChange: Function; onChange: Function;
onTouched: Function; onTouched: Function;
constructor(private cd: NgControl) { constructor(private cd: NgControl, private renderer: Renderer, private elementRef: ElementRef) {
this.onChange = (_) => {}; this.onChange = (_) => {};
this.onTouched = (_) => {}; this.onTouched = (_) => {};
cd.valueAccessor = this; cd.valueAccessor = this;
} }
writeValue(value) { this.checked = value; } writeValue(value) {
// both this.checked and setProperty are required at the moment
// remove when a proper imperative API is provided
this.checked = value;
setProperty(this.renderer, this.elementRef, "checked", value);
}
registerOnChange(fn): void { this.onChange = fn; } registerOnChange(fn): void { this.onChange = fn; }
registerOnTouched(fn): void { this.onTouched = fn; } registerOnTouched(fn): void { this.onTouched = fn; }

View File

@ -1,7 +1,8 @@
import {Directive} from 'angular2/angular2'; import {Directive, Renderer, ElementRef} from 'angular2/angular2';
import {NgControl} from './ng_control'; import {NgControl} from './ng_control';
import {ControlValueAccessor} from './control_value_accessor'; import {ControlValueAccessor} from './control_value_accessor';
import {isBlank} from 'angular2/src/facade/lang'; import {isBlank} from 'angular2/src/facade/lang';
import {setProperty} from './shared';
/** /**
* The default accessor for writing a value and listening to changes that is used by the * The default accessor for writing a value and listening to changes that is used by the
@ -35,13 +36,18 @@ export class DefaultValueAccessor implements ControlValueAccessor {
onChange: Function; onChange: Function;
onTouched: Function; onTouched: Function;
constructor(private cd: NgControl) { constructor(private cd: NgControl, private renderer: Renderer, private elementRef: ElementRef) {
this.onChange = (_) => {}; this.onChange = (_) => {};
this.onTouched = (_) => {}; this.onTouched = (_) => {};
cd.valueAccessor = this; cd.valueAccessor = this;
} }
writeValue(value) { this.value = isBlank(value) ? "" : value; } writeValue(value) {
// both this.value and setProperty are required at the moment
// remove when a proper imperative API is provided
this.value = isBlank(value) ? '' : value;
setProperty(this.renderer, this.elementRef, 'value', this.value);
}
registerOnChange(fn): void { this.onChange = fn; } registerOnChange(fn): void { this.onChange = fn; }

View File

@ -1,6 +1,22 @@
import {Directive} from 'angular2/angular2'; import {Directive, Query, QueryList, Renderer, ElementRef} from 'angular2/angular2';
import {NgControl} from './ng_control'; import {NgControl} from './ng_control';
import {ControlValueAccessor} from './control_value_accessor'; import {ControlValueAccessor} from './control_value_accessor';
import {setProperty} from './shared';
/**
* Marks <option> as dynamic, so Angular can be notified when options change.
*
* #Example:
* ```
* <select ng-control="city">
* <option *ng-for="#c of cities" [value]="c"></option>
* </select>
* ``
* @exportedAs angular2/forms
*/
@Directive({selector: 'option'})
export class NgSelectOption {
}
/** /**
* The accessor for writing a value and listening to changes on a select element. * The accessor for writing a value and listening to changes on a select element.
@ -23,19 +39,30 @@ import {ControlValueAccessor} from './control_value_accessor';
} }
}) })
export class SelectControlValueAccessor implements ControlValueAccessor { export class SelectControlValueAccessor implements ControlValueAccessor {
value = null; value = '';
onChange: Function; onChange: Function;
onTouched: Function; onTouched: Function;
constructor(private cd: NgControl) { constructor(private cd: NgControl, private renderer: Renderer, private elementRef: ElementRef,
@Query(NgSelectOption, {descendants: true}) query: QueryList<NgSelectOption>) {
this.onChange = (_) => {}; this.onChange = (_) => {};
this.onTouched = (_) => {}; this.onTouched = (_) => {};
this.value = '';
cd.valueAccessor = this; cd.valueAccessor = this;
this._updateValueWhenListOfOptionsChanges(query);
} }
writeValue(value) { this.value = value; } writeValue(value) {
// both this.value and setProperty are required at the moment
// remove when a proper imperative API is provided
this.value = value;
setProperty(this.renderer, this.elementRef, "value", value);
}
registerOnChange(fn): void { this.onChange = fn; } registerOnChange(fn): void { this.onChange = fn; }
registerOnTouched(fn): void { this.onTouched = fn; } registerOnTouched(fn): void { this.onTouched = fn; }
private _updateValueWhenListOfOptionsChanges(query: QueryList<NgSelectOption>) {
query.onChange(() => this.writeValue(this.value));
}
} }

View File

@ -5,6 +5,8 @@ import {ControlContainer} from './control_container';
import {NgControl} from './ng_control'; import {NgControl} from './ng_control';
import {Control} from '../model'; import {Control} from '../model';
import {Validators} from '../validators'; import {Validators} from '../validators';
import {Renderer, ElementRef} from 'angular2/angular2';
export function controlPath(name, parent: ControlContainer) { export function controlPath(name, parent: ControlContainer) {
var p = ListWrapper.clone(parent.path); var p = ListWrapper.clone(parent.path);
@ -37,3 +39,9 @@ function _throwError(dir: NgControl, message: string): void {
var path = ListWrapper.join(dir.path, " -> "); var path = ListWrapper.join(dir.path, " -> ");
throw new BaseException(`${message} '${path}'`); throw new BaseException(`${message} '${path}'`);
} }
export function setProperty(renderer: Renderer, elementRef: ElementRef, propName: string,
propValue: any) {
renderer.setElementProperty(elementRef.parentView.render, elementRef.boundElementIndex, propName,
propValue);
}

View File

@ -19,7 +19,7 @@ import {
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {TestBed} from 'angular2/src/test_lib/test_bed'; import {TestBed} from 'angular2/src/test_lib/test_bed';
import {NgIf} from 'angular2/directives'; import {NgIf, NgFor} from 'angular2/directives';
import { import {
Control, Control,
@ -300,6 +300,28 @@ export function main() {
}); });
})); }));
it("should support <select> with a dynamic list of options",
inject([TestBed], fakeAsync((tb: TestBed) => {
var ctx = MyComp.create(
{form: new ControlGroup({"city": new Control("NYC")}), data: ['SF', 'NYC']});
var t = `<div [ng-form-model]="form">
<select ng-control="city">
<option *ng-for="#c of data" [value]="c"></option>
</select>
</div>`;
tb.createView(MyComp, {context: ctx, html: t})
.then((view) => {
view.detectChanges();
tick();
var select = view.querySelector('select');
expect(select.value).toEqual('NYC');
});
})));
it("should support custom value accessors", it("should support custom value accessors",
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => { inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
var ctx = MyComp.create({form: new ControlGroup({"name": new Control("aa")})}); var ctx = MyComp.create({form: new ControlGroup({"name": new Control("aa")})});
@ -752,15 +774,17 @@ class WrappedValue implements ControlValueAccessor {
} }
@Component({selector: "my-comp"}) @Component({selector: "my-comp"})
@View({directives: [formDirectives, WrappedValue, NgIf]}) @View({directives: [formDirectives, WrappedValue, NgIf, NgFor]})
class MyComp { class MyComp {
form: any; form: any;
name: string; name: string;
data: any;
static create({form, name}: {form?: any, name?: any}) { static create({form, name, data}: {form?: any, name?: any, data?: any}) {
var mc = new MyComp(); var mc = new MyComp();
mc.form = form; mc.form = form;
mc.name = name; mc.name = name;
mc.data = data;
return mc; return mc;
} }
} }