X Tutup
Skip to content

Commit 74e2bd7

Browse files
committed
fix(select): support objects as select values
Closes #4843 Closes #7842
1 parent 52d3980 commit 74e2bd7

File tree

4 files changed

+323
-69
lines changed

4 files changed

+323
-69
lines changed
Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,35 @@
11
import {
2-
Query,
32
Directive,
43
Renderer,
5-
Self,
64
forwardRef,
75
Provider,
86
ElementRef,
9-
QueryList
7+
Input,
8+
Host,
9+
OnDestroy,
10+
Optional
1011
} from 'angular2/core';
11-
12-
import {ObservableWrapper} from 'angular2/src/facade/async';
1312
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
14-
import {CONST_EXPR} from 'angular2/src/facade/lang';
13+
import {
14+
CONST_EXPR,
15+
StringWrapper,
16+
isPrimitive,
17+
isPresent,
18+
looseIdentical
19+
} from 'angular2/src/facade/lang';
20+
21+
import {MapWrapper} from 'angular2/src/facade/collection';
1522

1623
const SELECT_VALUE_ACCESSOR = CONST_EXPR(new Provider(
1724
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => SelectControlValueAccessor), multi: true}));
1825

19-
/**
20-
* Marks `<option>` as dynamic, so Angular can be notified when options change.
21-
*
22-
* ### Example
23-
*
24-
* ```
25-
* <select ngControl="city">
26-
* <option *ngFor="#c of cities" [value]="c"></option>
27-
* </select>
28-
* ```
29-
*/
30-
@Directive({selector: 'option'})
31-
export class NgSelectOption {
26+
function _buildValueString(id: string, value: any): string {
27+
if (!isPrimitive(value)) value = "Object";
28+
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
29+
}
30+
31+
function _extractId(valueString: string): string {
32+
return valueString.split(":")[0];
3233
}
3334

3435
/**
@@ -37,27 +38,77 @@ export class NgSelectOption {
3738
@Directive({
3839
selector: 'select[ngControl],select[ngFormControl],select[ngModel]',
3940
host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
40-
bindings: [SELECT_VALUE_ACCESSOR]
41+
providers: [SELECT_VALUE_ACCESSOR]
4142
})
4243
export class SelectControlValueAccessor implements ControlValueAccessor {
43-
value: string;
44+
value: any;
45+
_optionMap: Map<string, any> = new Map<string, any>();
46+
_idCounter: number = 0;
47+
4448
onChange = (_: any) => {};
4549
onTouched = () => {};
4650

47-
constructor(private _renderer: Renderer, private _elementRef: ElementRef,
48-
@Query(NgSelectOption, {descendants: true}) query: QueryList<NgSelectOption>) {
49-
this._updateValueWhenListOfOptionsChanges(query);
50-
}
51+
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
5152

5253
writeValue(value: any): void {
5354
this.value = value;
54-
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', value);
55+
var valueString = _buildValueString(this._getOptionId(value), value);
56+
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', valueString);
5557
}
5658

57-
registerOnChange(fn: () => any): void { this.onChange = fn; }
59+
registerOnChange(fn: (value: any) => any): void {
60+
this.onChange = (valueString: string) => { fn(this._getOptionValue(valueString)); };
61+
}
5862
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
5963

60-
private _updateValueWhenListOfOptionsChanges(query: QueryList<NgSelectOption>) {
61-
ObservableWrapper.subscribe(query.changes, (_) => this.writeValue(this.value));
64+
_registerOption(): string { return (this._idCounter++).toString(); }
65+
66+
_getOptionId(value: any): string {
67+
for (let id of MapWrapper.keys(this._optionMap)) {
68+
if (looseIdentical(this._optionMap.get(id), value)) return id;
69+
}
70+
return null;
71+
}
72+
73+
_getOptionValue(valueString: string): any { return this._optionMap.get(_extractId(valueString)); }
74+
}
75+
76+
/**
77+
* Marks `<option>` as dynamic, so Angular can be notified when options change.
78+
*
79+
* ### Example
80+
*
81+
* ```
82+
* <select ngControl="city">
83+
* <option *ngFor="#c of cities" [value]="c"></option>
84+
* </select>
85+
* ```
86+
*/
87+
@Directive({selector: 'option'})
88+
export class NgSelectOption implements OnDestroy {
89+
id: string;
90+
91+
constructor(private _element: ElementRef, private _renderer: Renderer,
92+
@Optional() @Host() private _select: SelectControlValueAccessor) {
93+
if (isPresent(this._select)) this.id = this._select._registerOption();
94+
}
95+
96+
@Input()
97+
set value(value: any) {
98+
if (this._select == null) return;
99+
this._select._optionMap.set(this.id, value);
100+
this._setElementValue(_buildValueString(this.id, value));
101+
this._select.writeValue(this._select.value);
102+
}
103+
104+
_setElementValue(value: string): void {
105+
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
106+
}
107+
108+
ngOnDestroy() {
109+
if (isPresent(this._select)) {
110+
this._select._optionMap.delete(this.id);
111+
this._select.writeValue(this._select.value);
112+
}
62113
}
63114
}

modules/angular2/test/common/forms/directives_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,14 @@ export function main() {
9494
});
9595

9696
it("should return select accessor when provided", () => {
97-
var selectAccessor = new SelectControlValueAccessor(null, null, new QueryList<any>());
97+
var selectAccessor = new SelectControlValueAccessor(null, null);
9898
expect(selectValueAccessor(dir, [defaultAccessor, selectAccessor]))
9999
.toEqual(selectAccessor);
100100
});
101101

102102
it("should throw when more than one build-in accessor is provided", () => {
103103
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
104-
var selectAccessor = new SelectControlValueAccessor(null, null, new QueryList<any>());
104+
var selectAccessor = new SelectControlValueAccessor(null, null);
105105
expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError();
106106
});
107107

0 commit comments

Comments
 (0)
X Tutup