Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 89 additions & 22 deletions lib/ng2-select2.component.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -13,9 +16,14 @@ import { Select2OptionData } from './ng2-select2.interface';
</ng-content>
</select>`,
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
Expand All @@ -37,34 +45,91 @@ export class Select2Component implements AfterViewInit, OnChanges, OnDestroy, On
@Input() options: Select2Options;

// emitter when value is changed
@Output() valueChanged = new EventEmitter();
@Output() valueChanged: EventEmitter<ValueChangedEmition> = new EventEmitter<ValueChangedEmition>();

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');
this.renderer.setElementProperty(newLink, 'innerHTML', this.style);
}

}

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;
Expand All @@ -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);
Expand All @@ -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);
}
}
Expand All @@ -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.");
}
Expand All @@ -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);
Expand All @@ -147,26 +214,26 @@ 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<any> {
private async requireOldMatcher(): Promise<any> {
return new Promise<any[]>(resolve => {
jQuery.fn.select2.amd.require(['select2/compat/matcher'], (oldMatcher: any) => {
resolve(oldMatcher);
});
});
}

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);
}
Expand Down
9 changes: 8 additions & 1 deletion lib/ng2-select2.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
14 changes: 12 additions & 2 deletions lib/ng2-select2.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
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';

@NgModule({
declarations: [Select2Component],
exports: [Select2Component]
})
export class Select2Module {}
export class Select2Module {
static forRoot(config: any): ModuleWithProviders {
return {
ngModule: Select2Module,
providers: [
{ provide: Select2OptionInject, useValue: config }
]
};
}
}