Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
import {
Query,
Directive,
Renderer,
Self,
forwardRef,
Provider,
ElementRef,
QueryList
Input,
Host,
OnDestroy,
Optional
} from 'angular2/core';

import {ObservableWrapper} from 'angular2/src/facade/async';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
import {CONST_EXPR} from 'angular2/src/facade/lang';
import {
CONST_EXPR,
StringWrapper,
isPrimitive,
isPresent,
looseIdentical
} from 'angular2/src/facade/lang';

import {MapWrapper} from 'angular2/src/facade/collection';

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

/**
* Marks `<option>` as dynamic, so Angular can be notified when options change.
*
* ### Example
*
* ```
* <select ngControl="city">
* <option *ngFor="#c of cities" [value]="c"></option>
* </select>
* ```
*/
@Directive({selector: 'option'})
export class NgSelectOption {
function _buildValueString(id: string, value: any): string {
if (!isPrimitive(value)) value = "Object";
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
}

function _extractId(valueString: string): string {
return valueString.split(":")[0];
}

/**
Expand All @@ -37,27 +38,77 @@ export class NgSelectOption {
@Directive({
selector: 'select[ngControl],select[ngFormControl],select[ngModel]',
host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
bindings: [SELECT_VALUE_ACCESSOR]
providers: [SELECT_VALUE_ACCESSOR]
})
export class SelectControlValueAccessor implements ControlValueAccessor {
value: string;
value: any;
_optionMap: Map<string, any> = new Map<string, any>();
_idCounter: number = 0;

onChange = (_: any) => {};
onTouched = () => {};

constructor(private _renderer: Renderer, private _elementRef: ElementRef,
@Query(NgSelectOption, {descendants: true}) query: QueryList<NgSelectOption>) {
this._updateValueWhenListOfOptionsChanges(query);
}
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}

writeValue(value: any): void {
this.value = value;
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', value);
var valueString = _buildValueString(this._getOptionId(value), value);
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', valueString);
}

registerOnChange(fn: () => any): void { this.onChange = fn; }
registerOnChange(fn: (value: any) => any): void {
this.onChange = (valueString: string) => { fn(this._getOptionValue(valueString)); };
}
registerOnTouched(fn: () => any): void { this.onTouched = fn; }

private _updateValueWhenListOfOptionsChanges(query: QueryList<NgSelectOption>) {
ObservableWrapper.subscribe(query.changes, (_) => this.writeValue(this.value));
_registerOption(): string { return (this._idCounter++).toString(); }

_getOptionId(value: any): string {
for (let id of MapWrapper.keys(this._optionMap)) {
if (looseIdentical(this._optionMap.get(id), value)) return id;
}
return null;
}

_getOptionValue(valueString: string): any { return this._optionMap.get(_extractId(valueString)); }
}

/**
* Marks `<option>` as dynamic, so Angular can be notified when options change.
*
* ### Example
*
* ```
* <select ngControl="city">
* <option *ngFor="#c of cities" [value]="c"></option>
* </select>
* ```
*/
@Directive({selector: 'option'})
export class NgSelectOption implements OnDestroy {
id: string;

constructor(private _element: ElementRef, private _renderer: Renderer,
@Optional() @Host() private _select: SelectControlValueAccessor) {
if (isPresent(this._select)) this.id = this._select._registerOption();
}

@Input()
set value(value: any) {
if (this._select == null) return;
this._select._optionMap.set(this.id, value);
this._setElementValue(_buildValueString(this.id, value));
this._select.writeValue(this._select.value);
}

_setElementValue(value: string): void {
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
}

ngOnDestroy() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be mistaken, but I think you need to call _select.writeValue here. And add a test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, added

if (isPresent(this._select)) {
this._select._optionMap.delete(this.id);
this._select.writeValue(this._select.value);
}
}
}
4 changes: 2 additions & 2 deletions modules/angular2/test/common/forms/directives_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,14 @@ export function main() {
});

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

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

Expand Down
Loading