Skip to content

Commit

Permalink
feat(core): Implemented MultiSelectControlValueAccessorDirective
Browse files Browse the repository at this point in the history
relates #14
relates #5
  • Loading branch information
Airblader committed Dec 21, 2018
1 parent 7fcb598 commit bff5d3f
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 10 deletions.
3 changes: 2 additions & 1 deletion projects/ngqp/core/src/lib/accessors/accessors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export { DefaultControlValueAccessorDirective } from './default-control-value-ac
export { NumberControlValueAccessorDirective } from './number-control-value-accessor.directive';
export { RangeControlValueAccessorDirective } from './range-control-value-accessor.directive';
export { CheckboxControlValueAccessorDirective } from './checkbox-control-value-accessor.directive';
export { SelectControlValueAccessorDirective, NgqpSelectOptionDirective } from './select-control-value-accessor.directive';
export { SelectControlValueAccessorDirective, SelectOptionDirective } from './select-control-value-accessor.directive';
export { MultiSelectControlValueAccessorDirective, MultiSelectOptionDirective } from './multi-select-control-value-accessor.directive';
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import {
Directive,
ElementRef,
forwardRef,
Host,
HostListener,
Input,
OnDestroy,
OnInit,
Optional,
Renderer2,
StaticProvider
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

export const NGQP_MULTI_SELECT_VALUE_ACCESSOR: StaticProvider = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MultiSelectControlValueAccessorDirective),
multi: true
};

@Directive({
selector: 'select[multiple][queryParamName]',
providers: [NGQP_MULTI_SELECT_VALUE_ACCESSOR],
})
export class MultiSelectControlValueAccessorDirective<T> implements ControlValueAccessor {

private selectedIds: string[] = [];
private options = new Map<string, MultiSelectOptionDirective<T>>();
private optionMap = new Map<string, T>();

private idCounter = 0;
private fnChange = (_: T[]) => {};
private fnTouched = () => {};

@HostListener('change')
public onChange() {
this.selectedIds = Array.from(this.options.entries())
.filter(([id, option]) => option.selected)
.map(([id]) => id);
const values = this.selectedIds.map(id => this.optionMap.get(id));
this.fnChange(values);
}

@HostListener('blur')
public onTouched() {
this.fnTouched();
}

constructor(private renderer: Renderer2, private elementRef: ElementRef) {
}

public writeValue(values: T[]) {
values = values === null ? <T[]>[] : values;
if (!Array.isArray(values)) {
throw new Error(`Provided a non-array value to select[multiple]: ${values}`);
}

this.selectedIds = values
.map(value => this.getOptionId(value))
.filter(id => id !== null);
this.options.forEach((option, id) => option.selected = this.selectedIds.includes(id));
}

public registerOnChange(fn: any) {
this.fnChange = fn;
}

public registerOnTouched(fn: any) {
this.fnTouched = fn;
}

public setDisabledState(isDisabled: boolean) {
this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
}

public registerOption(option: MultiSelectOptionDirective<T>): string {
const newId = (this.idCounter++).toString();
this.options.set(newId, option);
return newId;
}

public deregisterOption(id: string): void {
this.optionMap.delete(id);
}

public updateOptionValue(id: string, value: T): void {
this.optionMap.set(id, value);
if (this.selectedIds.includes(id)) {
this.onChange();
}
}

private getOptionId(value: T): string | null {
for (const id of Array.from(this.optionMap.keys())) {
if (this.optionMap.get(id) === value) {
return id;
}
}

return null;
}

}

@Directive({
selector: 'option',
})
export class MultiSelectOptionDirective<T> implements OnInit, OnDestroy {

private readonly id: string | null = null;

constructor(
@Optional() @Host() private parent: MultiSelectControlValueAccessorDirective<T>,
private renderer: Renderer2,
private elementRef: ElementRef,
) {
this.id = this.parent.registerOption(this);
}

public ngOnInit() {
this.renderer.setProperty(this.elementRef.nativeElement, 'value', this.id);
}

public ngOnDestroy() {
this.parent.deregisterOption(this.id);
}

@Input('value')
public set value(value: T) {
this.parent.updateOptionValue(this.id, value);
}

public get selected(): boolean {
return (this.elementRef.nativeElement as HTMLOptionElement).selected;
}

public set selected(selected: boolean) {
this.renderer.setProperty(this.elementRef.nativeElement, 'selected', this.selected);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
Host,
HostListener,
Input,
OnDestroy,
OnDestroy, OnInit,
Optional,
Renderer2,
StaticProvider
Expand Down Expand Up @@ -103,7 +103,7 @@ export class SelectControlValueAccessorDirective<T> implements ControlValueAcces
@Directive({
selector: 'option',
})
export class NgqpSelectOptionDirective<T> implements OnDestroy {
export class SelectOptionDirective<T> implements OnInit, OnDestroy {

private readonly id: string | null = null;

Expand All @@ -112,9 +112,11 @@ export class NgqpSelectOptionDirective<T> implements OnDestroy {
private renderer: Renderer2,
private elementRef: ElementRef,
) {
if (this.parent) {
this.id = this.parent.registerOption();
}
this.id = this.parent.registerOption();
}

public ngOnInit() {
this.renderer.setProperty(this.elementRef.nativeElement, 'value', this.id);
}

public ngOnDestroy() {
Expand All @@ -124,7 +126,6 @@ export class NgqpSelectOptionDirective<T> implements OnDestroy {

@Input('value')
public set value(value: T) {
this.renderer.setProperty(this.elementRef.nativeElement, 'value', this.id);
this.parent.updateOptionValue(this.id, value);
}

Expand Down
10 changes: 7 additions & 3 deletions projects/ngqp/core/src/lib/query-param.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { QueryParamGroupDirective } from './query-param-group.directive';
import {
CheckboxControlValueAccessorDirective,
DefaultControlValueAccessorDirective,
NgqpSelectOptionDirective,
MultiSelectControlValueAccessorDirective,
MultiSelectOptionDirective,
NumberControlValueAccessorDirective,
RangeControlValueAccessorDirective,
SelectControlValueAccessorDirective
SelectControlValueAccessorDirective,
SelectOptionDirective
} from './accessors/accessors';
import { DefaultRouterAdapter, NGQP_ROUTER_ADAPTER } from './router-adapter/router-adapter';

Expand All @@ -21,7 +23,9 @@ const DIRECTIVES: Type<any>[] = [
RangeControlValueAccessorDirective,
CheckboxControlValueAccessorDirective,
SelectControlValueAccessorDirective,
NgqpSelectOptionDirective,
SelectOptionDirective,
MultiSelectControlValueAccessorDirective,
MultiSelectOptionDirective,
];

@NgModule({
Expand Down

0 comments on commit bff5d3f

Please sign in to comment.