fix(Control): Support <select multiple> with Control class (#8069)

This commit is contained in:
Nicholas Hydock 2016-05-26 15:01:49 -04:00 committed by Miško Hevery
parent cbc8d0adf8
commit 84f859d7b2
3 changed files with 205 additions and 1 deletions

View File

@ -14,6 +14,10 @@ import {
SelectControlValueAccessor, SelectControlValueAccessor,
NgSelectOption NgSelectOption
} from './directives/select_control_value_accessor'; } from './directives/select_control_value_accessor';
import {
SelectMultipleControlValueAccessor,
NgSelectMultipleOption
} from './directives/select_multiple_control_value_accessor';
import { import {
RequiredValidator, RequiredValidator,
MinLengthValidator, MinLengthValidator,
@ -39,6 +43,10 @@ export {
SelectControlValueAccessor, SelectControlValueAccessor,
NgSelectOption NgSelectOption
} from './directives/select_control_value_accessor'; } from './directives/select_control_value_accessor';
export {
SelectMultipleControlValueAccessor,
NgSelectMultipleOption
} from './directives/select_multiple_control_value_accessor';
export { export {
RequiredValidator, RequiredValidator,
MinLengthValidator, MinLengthValidator,
@ -74,10 +82,12 @@ export const FORM_DIRECTIVES: Type[] = /*@ts2dart_const*/[
NgForm, NgForm,
NgSelectOption, NgSelectOption,
NgSelectMultipleOption,
DefaultValueAccessor, DefaultValueAccessor,
NumberValueAccessor, NumberValueAccessor,
CheckboxControlValueAccessor, CheckboxControlValueAccessor,
SelectControlValueAccessor, SelectControlValueAccessor,
SelectMultipleControlValueAccessor,
RadioControlValueAccessor, RadioControlValueAccessor,
NgControlStatus, NgControlStatus,

View File

@ -45,7 +45,8 @@ function _extractId(valueString: string): string {
* *
*/ */
@Directive({ @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()'}, host: {'(change)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
providers: [SELECT_VALUE_ACCESSOR] providers: [SELECT_VALUE_ACCESSOR]
}) })

View File

@ -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<string, NgSelectMultipleOption> = new Map<string, NgSelectMultipleOption>();
/** @internal */
_idCounter: number = 0;
onChange = (_: any) => {};
onTouched = () => {};
constructor() {}
writeValue(value: any): void {
this.value = value;
if (value == null) return;
let values: Array<any> = <Array<any>>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<any> = [];
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 = <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 `<option>` as dynamic, so Angular can be notified when options change.
*
* ### Example
*
* ```
* <select multiple ngControl="city">
* <option *ngFor="let c of cities" [value]="c"></option>
* </select>
* ```
*/
@Directive({selector: 'option'})
export class NgSelectMultipleOption implements OnDestroy {
id: string;
/** @internal */
_value: any;
constructor(private _element: ElementRef, private _renderer: Renderer,
@Optional() @Host() private _select: SelectMultipleControlValueAccessor) {
if (isPresent(this._select)) {
this.id = this._select._registerOption(this);
}
}
@Input('ngValue')
set ngValue(value: any) {
if (this._select == null) return;
this._value = value;
this._setElementValue(_buildValueString(this.id, value));
this._select.writeValue(this._select.value);
}
@Input('value')
set value(value: any) {
if (isPresent(this._select)) {
this._value = value;
this._setElementValue(_buildValueString(this.id, value));
this._select.writeValue(this._select.value);
} else {
this._setElementValue(value);
}
}
/** @internal */
_setElementValue(value: string): void {
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
}
/** @internal */
_setSelected(selected: boolean) {
this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected);
}
ngOnDestroy() {
if (isPresent(this._select)) {
this._select._optionMap.delete(this.id);
this._select.writeValue(this._select.value);
}
}
}
export const SELECT_DIRECTIVES = [SelectMultipleControlValueAccessor, NgSelectMultipleOption];