diff --git a/lib/ng2-select2.component.ts b/lib/ng2-select2.component.ts index 959a334..773c422 100644 --- a/lib/ng2-select2.component.ts +++ b/lib/ng2-select2.component.ts @@ -1,9 +1,12 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, - Output, SimpleChanges, ViewChild, ViewEncapsulation, Renderer, OnInit + Output, SimpleChanges, ViewChild, ViewEncapsulation, Renderer, OnInit, forwardRef, SimpleChange, Optional } from '@angular/core'; -import { Select2OptionData } from './ng2-select2.interface'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; + +import { Select2OptionData, Select2OptionInject, ValueChangedEmition } from './ng2-select2.interface'; +import { Subscription } from 'rxjs'; @Component({ selector: 'select2', @@ -13,9 +16,14 @@ import { Select2OptionData } from './ng2-select2.interface'; `, encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => Select2Component), + multi: true + }] }) -export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, OnInit { +export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, OnInit, ControlValueAccessor { @ViewChild('selector') selector: ElementRef; // data for select2 drop down @@ -37,19 +45,70 @@ export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, On @Input() options: Select2Options; // emitter when value is changed - @Output() valueChanged = new EventEmitter(); + @Output() valueChanged: EventEmitter = new EventEmitter(); private element: JQuery = undefined; private check: boolean = false; + private subs: Subscription[] = []; + + constructor(private renderer: Renderer, @Optional() config: Select2OptionInject) { + if (config != null) this.options = config; + } + + /////////////// + // OVERRIDES // + /////////////// + + /** + * Invoked when the model has been changed + */ + onChange: (_: any) => void = (_: any) => { }; + + /** + * Invoked when the model has been touched + */ + onTouched: () => void = () => { }; + + firstChange: boolean = true; + + /** + * Writes a new item to the element. + * @param value the value + */ + writeValue(value: string): void { + + let change = new SimpleChange(this.value, value, this.firstChange); + + this.value = value; + this.firstChange = false; - constructor(private renderer: Renderer) { } + this.ngOnChanges({ + "value": change + }); + } + + /** + * Registers a callback function that should be called when the control's value changes in the UI. + * @param fn + */ + registerOnChange(fn: any): void { + this.onChange = fn; + } + + /** + * Registers a callback function that should be called when the control receives a blur event. + * @param fn + */ + registerOnTouched(fn: any): void { + this.onTouched = fn; + } ngOnInit() { - if(this.cssImport) { + if (this.cssImport) { const head = document.getElementsByTagName('head')[0]; - const link: any = head.children[head.children.length-1]; + const link: any = head.children[head.children.length - 1]; - if(!link.version) { + if (!link.version) { const newLink = this.renderer.createElement(head, 'style'); this.renderer.setElementProperty(newLink, 'type', 'text/css'); this.renderer.setElementProperty(newLink, 'version', 'select2'); @@ -57,14 +116,20 @@ export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, On } } + + this.subs.push(this.valueChanged.subscribe(v => { + if (typeof this.onChange == 'function') { + this.onChange(v.value); + } + })); } async ngOnChanges(changes: SimpleChanges) { - if(!this.element) { + if (!this.element) { return; } - if(changes['data'] && JSON.stringify(changes['data'].previousValue) !== JSON.stringify(changes['data'].currentValue)) { + if (changes['data'] && JSON.stringify(changes['data'].previousValue) !== JSON.stringify(changes['data'].currentValue)) { await this.initPlugin(); const newValue: string = this.element.val() as string; @@ -74,7 +139,7 @@ export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, On }); } - if(changes['value'] && changes['value'].previousValue !== changes['value'].currentValue) { + if (changes['value'] && changes['value'].previousValue !== changes['value'].currentValue) { const newValue: string = changes['value'].currentValue; this.setElementValue(newValue); @@ -85,7 +150,7 @@ export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, On }); } - if(changes['disabled'] && changes['disabled'].previousValue !== changes['disabled'].currentValue) { + if (changes['disabled'] && changes['disabled'].previousValue !== changes['disabled'].currentValue) { this.renderer.setElementProperty(this.selector.nativeElement, 'disabled', this.disabled); } } @@ -110,11 +175,13 @@ export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, On if (this.element && this.element.off) { this.element.off("select2:select"); } + + for (let s of this.subs) if (s) s.unsubscribe(); } private async initPlugin() { - if(!this.element.select2) { - if(!this.check) { + if (!this.element.select2) { + if (!this.check) { this.check = true; console.log("Please add Select2 library (js file) to the project. You can download it from https://github.com/select2/select2/tree/master/dist/js."); } @@ -128,14 +195,14 @@ export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, On this.renderer.setElementProperty(this.selector.nativeElement, 'innerHTML', ''); } - let options: Select2Options = { + let options: any = { data: this.data, width: (this.width) ? this.width : 'resolve' }; Object.assign(options, this.options); - if(options.matcher) { + if (options.matcher) { let oldMatcher: any = await this.requireOldMatcher(); options.matcher = oldMatcher(options.matcher); this.element.select2(options); @@ -147,12 +214,12 @@ export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, On this.element.select2(options); } - if(this.disabled) { + if (this.disabled) { this.renderer.setElementProperty(this.selector.nativeElement, 'disabled', this.disabled); } } - private async requireOldMatcher() : Promise { + private async requireOldMatcher(): Promise { return new Promise(resolve => { jQuery.fn.select2.amd.require(['select2/compat/matcher'], (oldMatcher: any) => { resolve(oldMatcher); @@ -160,13 +227,13 @@ export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, On }); } - private setElementValue (newValue: string | string[]) { - if(Array.isArray(newValue)) { + private setElementValue(newValue: string | string[]) { + if (Array.isArray(newValue)) { for (let option of this.selector.nativeElement.options) { if (newValue.indexOf(option.value) > -1) { this.renderer.setElementProperty(option, 'selected', 'true'); } - } + } } else { this.renderer.setElementProperty(this.selector.nativeElement, 'value', newValue); } diff --git a/lib/ng2-select2.interface.ts b/lib/ng2-select2.interface.ts index f93863e..3802216 100644 --- a/lib/ng2-select2.interface.ts +++ b/lib/ng2-select2.interface.ts @@ -7,5 +7,12 @@ export interface Select2OptionData { } export interface Select2TemplateFunction { - (state: Select2OptionData): JQuery | string; + (state: Select2OptionData): string; +} + +export class Select2OptionInject { } + +export interface ValueChangedEmition { + value: string | string[]; + data?: any; } diff --git a/lib/ng2-select2.ts b/lib/ng2-select2.ts index b35c374..7206f2b 100644 --- a/lib/ng2-select2.ts +++ b/lib/ng2-select2.ts @@ -1,7 +1,8 @@ -import { NgModule } from '@angular/core'; +import { NgModule, ModuleWithProviders } from '@angular/core'; export { Select2OptionData, Select2TemplateFunction } from './ng2-select2.interface'; import { Select2Component } from './ng2-select2.component'; +import { Select2OptionInject } from './ng2-select2.interface'; export { Select2Component } from './ng2-select2.component'; @@ -9,4 +10,13 @@ export { Select2Component } from './ng2-select2.component'; declarations: [Select2Component], exports: [Select2Component] }) -export class Select2Module {} \ No newline at end of file +export class Select2Module { + static forRoot(config: any): ModuleWithProviders { + return { + ngModule: Select2Module, + providers: [ + { provide: Select2OptionInject, useValue: config } + ] + }; + } +} \ No newline at end of file