fix(Control): Support <select multiple> with Control class (#8069)
This commit is contained in:
parent
cbc8d0adf8
commit
84f859d7b2
|
@ -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,
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
})
|
})
|
||||||
|
|
|
@ -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];
|
Loading…
Reference in New Issue