diff --git a/modules/@angular/common/src/forms/directives.ts b/modules/@angular/common/src/forms/directives.ts index f1b67e0b76d14..7ef3b2215c5cd 100644 --- a/modules/@angular/common/src/forms/directives.ts +++ b/modules/@angular/common/src/forms/directives.ts @@ -14,6 +14,10 @@ import { SelectControlValueAccessor, NgSelectOption } from './directives/select_control_value_accessor'; +import { + SelectMultipleControlValueAccessor, + NgSelectMultipleOption +} from './directives/select_multiple_control_value_accessor'; import { RequiredValidator, MinLengthValidator, @@ -39,6 +43,10 @@ export { SelectControlValueAccessor, NgSelectOption } from './directives/select_control_value_accessor'; +export { + SelectMultipleControlValueAccessor, + NgSelectMultipleOption +} from './directives/select_multiple_control_value_accessor'; export { RequiredValidator, MinLengthValidator, @@ -74,10 +82,12 @@ export const FORM_DIRECTIVES: Type[] = /*@ts2dart_const*/[ NgForm, NgSelectOption, + NgSelectMultipleOption, DefaultValueAccessor, NumberValueAccessor, CheckboxControlValueAccessor, SelectControlValueAccessor, + SelectMultipleControlValueAccessor, RadioControlValueAccessor, NgControlStatus, diff --git a/modules/@angular/common/src/forms/directives/select_control_value_accessor.ts b/modules/@angular/common/src/forms/directives/select_control_value_accessor.ts index fa93144ee74c3..3a90be3e1d3e0 100644 --- a/modules/@angular/common/src/forms/directives/select_control_value_accessor.ts +++ b/modules/@angular/common/src/forms/directives/select_control_value_accessor.ts @@ -45,7 +45,8 @@ function _extractId(valueString: string): string { * */ @Directive({ - selector: 'select[ngControl],select[ngFormControl],select[ngModel]', + selector: + 'select:not([multiple])[ngControl],select:not([multiple])[ngFormControl],select:not([multiple])[ngModel]', host: {'(change)': 'onChange($event.target.value)', '(blur)': 'onTouched()'}, providers: [SELECT_VALUE_ACCESSOR] }) diff --git a/modules/@angular/common/src/forms/directives/select_multiple_control_value_accessor.ts b/modules/@angular/common/src/forms/directives/select_multiple_control_value_accessor.ts new file mode 100644 index 0000000000000..7228f001a27b6 --- /dev/null +++ b/modules/@angular/common/src/forms/directives/select_multiple_control_value_accessor.ts @@ -0,0 +1,193 @@ +import { + Input, + Directive, + ElementRef, + Renderer, + Optional, + Host, + OnDestroy, + Provider, + forwardRef +} from "@angular/core"; +import { + isBlank, + isPrimitive, + StringWrapper, + isPresent, + looseIdentical, + isString +} from '../../../src/facade/lang'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; +import {MapWrapper} from '../../../src/facade/collection'; + +const SELECT_MULTIPLE_VALUE_ACCESSOR = { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SelectMultipleControlValueAccessor), + multi: true +}; + +function _buildValueString(id: string, value: any): string { + if (isBlank(id)) return `${value}`; + if (isString(value)) value = `'${value}'`; + if (!isPrimitive(value)) value = "Object"; + return StringWrapper.slice(`${id}: ${value}`, 0, 50); +} + +function _extractId(valueString: string): string { + return valueString.split(":")[0]; +} + +/** Mock interface for HTML Options */ +interface HTMLOption { + value: string; + selected: boolean; +} + +/** Mock interface for HTMLCollection */ +abstract class HTMLCollection { + length: number; + abstract item(_: number): HTMLOption; +} + +/** + * The accessor for writing a value and listening to changes on a select element. + */ +@Directive({ + selector: 'select[multiple][ngControl],select[multiple][ngFormControl],select[multiple][ngModel]', + host: {'(input)': 'onChange($event.target)', '(blur)': 'onTouched()'}, + providers: [SELECT_MULTIPLE_VALUE_ACCESSOR] +}) +export class SelectMultipleControlValueAccessor implements ControlValueAccessor { + value: any; + /** @internal */ + _optionMap: Map = new Map(); + /** @internal */ + _idCounter: number = 0; + + onChange = (_: any) => {}; + onTouched = () => {}; + + constructor() {} + + writeValue(value: any): void { + this.value = value; + if (value == null) return; + let values: Array = >value; + // convert values to ids + let ids = values.map((v) => this._getOptionId(v)); + this._optionMap.forEach((opt, o) => { + opt._setSelected(ids.indexOf(o.toString()) > -1); + }); + } + + registerOnChange(fn: (value: any) => any): void { + this.onChange = (_: any) => { + let selected: Array = []; + if (_.hasOwnProperty('selectedOptions')) { + let options: HTMLCollection = _.selectedOptions; + for (var i = 0; i < options.length; i++) { + let opt: any = options.item(i); + let val: any = this._getOptionValue(opt.value); + selected.push(val); + } + } + // Degrade on IE + else { + let options: HTMLCollection = _.options; + for (var i = 0; i < options.length; i++) { + let opt: HTMLOption = options.item(i); + if (opt.selected) { + let val: any = this._getOptionValue(opt.value); + selected.push(val); + } + } + } + fn(selected); + }; + } + registerOnTouched(fn: () => any): void { this.onTouched = fn; } + + /** @internal */ + _registerOption(value: NgSelectMultipleOption): string { + let id:string = (this._idCounter++).toString(); + this._optionMap.set(id, value); + return id; + } + + /** @internal */ + _getOptionId(value: any): string { + for (let id of MapWrapper.keys(this._optionMap)) { + if (looseIdentical(this._optionMap.get(id)._value, value)) return id; + } + return null; + } + + /** @internal */ + _getOptionValue(valueString: string): any { + let opt = this._optionMap.get(_extractId(valueString)); + return isPresent(opt) ? opt._value : valueString; + } +} + +/** + * Marks `