code cleanup, names refactoring
This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
||||
import {Directive, TemplateRef} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[ngSelectDsNotFoundTemplate]'
|
||||
})
|
||||
export class NotFoundTemplateDirective {
|
||||
constructor(public template: TemplateRef<any>) { }
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import {Directive, TemplateRef} from '@angular/core';
|
||||
|
||||
|
||||
@Directive({
|
||||
selector: '[ngSelectDsFooter]'
|
||||
})
|
||||
export class SelectFooterTemplateDirective {
|
||||
constructor(public template: TemplateRef<any>) { }
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { Directive, TemplateRef } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[ngSelectDsLabel]'
|
||||
})
|
||||
export class SelectLabelTemplateDirective {
|
||||
constructor(public template: TemplateRef<any>) { }
|
||||
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import {Directive, TemplateRef} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[ngSelectDsOption]'
|
||||
})
|
||||
export class SelectOptionTemplateDirective {
|
||||
constructor(public template: TemplateRef<any>) { }
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<ng-select [items]="data$ |async"
|
||||
[bindLabel]="bindLabel"
|
||||
[bindValue]="bindValue"
|
||||
autofocus
|
||||
[typeahead] = "searchInput$"
|
||||
[trackByFn]="trackFn"
|
||||
[searchable]="true"
|
||||
[multiple]="true"
|
||||
[placeholder]="placeholder"
|
||||
(change)="valueChanged($event)">
|
||||
<ng-container *ngIf="hasOptionTemplate">
|
||||
<ng-template ng-option-tmp let-item="item" let-index="index">
|
||||
<ng-container [ngTemplateOutlet]="selectOptionTemplate"
|
||||
[ngTemplateOutletContext]="{
|
||||
$implicit: item,
|
||||
index: index
|
||||
}"></ng-container>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="hasLabelTemplate">
|
||||
<ng-template ng-label-tmp let-item="item" let-index="index" let-clear="clear">
|
||||
<span class="ng-value-label">
|
||||
<ng-container [ngTemplateOutlet]="selectLabelTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: item, index: index, clear: clear }"></ng-container>
|
||||
</span>
|
||||
<span class="ng-value-icon right" (click)="clear(item)" aria-hidden="true">×</span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ng-select>
|
||||
<ng-container *ngIf="footerTemplate">
|
||||
<ng-container [ngTemplateOutlet]="footerTemplate.template" [ngTemplateOutletContext]="{ $implicit: selectedModel }">
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
import { Component, OnInit, ContentChild, ViewChild, Input, Output, EventEmitter, ChangeDetectorRef, forwardRef, OnDestroy } from '@angular/core';
|
||||
import { SelectLabelTemplateDirective } from '../directives/select-label-template.directive';
|
||||
import { IDataSource, ISimpleFilter, ISort } from '@poweredsoft/data';
|
||||
import { Observable, Subject, Subscription } from 'rxjs';
|
||||
import { map, distinctUntilChanged, debounceTime } from 'rxjs/operators';
|
||||
import { NgSelectComponent as SelectComponent } from '@ng-select/ng-select';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { SelectOptionTemplateDirective } from '../directives/select-option-template.directive';
|
||||
import { SelectFooterTemplateDirective } from '../directives/select-footer-template.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'ps-ng-multi-select',
|
||||
templateUrl: './multi-select.component.html',
|
||||
providers: [{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => MultiSelectComponent),
|
||||
multi: true
|
||||
}],
|
||||
styleUrls: ['./multi-select.component.scss']
|
||||
})
|
||||
export class MultiSelectComponent implements OnInit, OnDestroy {
|
||||
|
||||
|
||||
@ContentChild(SelectOptionTemplateDirective) optionTemplate: SelectOptionTemplateDirective;
|
||||
@ContentChild(SelectLabelTemplateDirective) labelTemplate: SelectLabelTemplateDirective;
|
||||
@ContentChild(SelectFooterTemplateDirective) footerTemplate: SelectFooterTemplateDirective;
|
||||
|
||||
@ViewChild(SelectComponent, { static: true }) selectComponent: SelectComponent;
|
||||
@Input() dataSource: IDataSource<any>;
|
||||
@Input() searchPath: string | string[];
|
||||
@Input() searchType: string;
|
||||
@Input() sortingPath: string | string[];
|
||||
@Input() serverFiltering: boolean;
|
||||
@Input() bindLabel: string;
|
||||
@Input() bindValue: string;
|
||||
@Input() placeholder: string;
|
||||
|
||||
@Output('change') changeEvent = new EventEmitter();
|
||||
|
||||
trackFn: (item: any) => any;
|
||||
data$: Observable<any[]>;
|
||||
isLoading: boolean = false;
|
||||
searchInput$ = new Subject<string>();
|
||||
|
||||
private _loadingSubscription: Subscription;
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
this.trackFn = this.trackBy.bind(this);
|
||||
|
||||
}
|
||||
|
||||
trackBy(item: any) {
|
||||
return this.dataSource.resolveIdField(item);
|
||||
}
|
||||
|
||||
valueChanged(event) {
|
||||
this.changeEvent.emit(event);
|
||||
}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.selectComponent.writeValue(obj);
|
||||
}
|
||||
registerOnChange(fn: any): void {
|
||||
this.selectComponent.registerOnChange(fn);
|
||||
}
|
||||
registerOnTouched(fn: any): void {
|
||||
this.selectComponent.registerOnTouched(fn);
|
||||
}
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
if (this.selectComponent.setDisabledState)
|
||||
this.selectComponent.setDisabledState(isDisabled);
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this._loadingSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dataFetching();
|
||||
this.detectLoading();
|
||||
if (this.serverFiltering) {
|
||||
this.searchOnServer();
|
||||
} else {
|
||||
this.refreshDataSource();
|
||||
}
|
||||
}
|
||||
|
||||
dataFetching() {
|
||||
this.data$ = this.dataSource.data$.pipe(
|
||||
map(t => {
|
||||
if (t == null)
|
||||
return [];
|
||||
return t.data;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
detectLoading() {
|
||||
this._loadingSubscription = this.dataSource.loading$.subscribe(loading => {
|
||||
this.isLoading = loading;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
searchOnServer() {
|
||||
this.searchInput$.pipe(
|
||||
distinctUntilChanged(), // emit the difference from previous input
|
||||
debounceTime(500) // this is for delaying searching speed
|
||||
).subscribe(searchTerm => this.refreshDataSource(searchTerm, 1, 100)); // page: 1, pageSize: 50
|
||||
|
||||
this.refreshDataSource(); //send the query to server to sorting & filtering by default
|
||||
}
|
||||
|
||||
get selectedModel() {
|
||||
return this.selectComponent.selectedItems.map(t => t.value);
|
||||
}
|
||||
|
||||
refreshDataSource(searchTerm: any = null, page: number = null, pageSize: number = null) {
|
||||
let searchfilters: ISimpleFilter[] = [];
|
||||
if (searchTerm == "")
|
||||
searchTerm = null;
|
||||
if (searchTerm) {
|
||||
if (this.searchPath) {
|
||||
if (Array.isArray(this.searchPath)) {
|
||||
searchfilters = this.searchPath.map(path => {
|
||||
return <ISimpleFilter>{
|
||||
path: path,
|
||||
type: 'Contains',
|
||||
value: searchTerm,
|
||||
and: false
|
||||
}
|
||||
});
|
||||
} else {
|
||||
searchfilters = [<ISimpleFilter>{
|
||||
path: this.searchPath,
|
||||
type: 'Contains', // Default: Contains
|
||||
value: searchTerm
|
||||
}]
|
||||
}
|
||||
} else {
|
||||
searchfilters = [<ISimpleFilter>{
|
||||
path: this.bindLabel,
|
||||
type: 'Contains', // Default: Contains
|
||||
value: searchTerm
|
||||
}]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let sorts: ISort[];
|
||||
if (this.sortingPath && Array.isArray(this.sortingPath)) {
|
||||
sorts = this.sortingPath.map(sortPath => (<ISort>{
|
||||
path: sortPath,
|
||||
ascending: true
|
||||
}));
|
||||
} else {
|
||||
sorts = [<ISort>{ path: this.sortingPath || this.bindLabel, ascending: true }];
|
||||
}
|
||||
|
||||
this.dataSource.query({
|
||||
page: page,
|
||||
pageSize: pageSize,
|
||||
filters: searchfilters,
|
||||
sorts: sorts
|
||||
});
|
||||
}
|
||||
|
||||
get hasOptionTemplate() {
|
||||
return this.optionTemplate ? true : false;
|
||||
}
|
||||
|
||||
get selectOptionTemplate() {
|
||||
if (this.optionTemplate)
|
||||
return this.optionTemplate.template;
|
||||
return null;
|
||||
}
|
||||
|
||||
get hasLabelTemplate() {
|
||||
return this.labelTemplate ? true : false;
|
||||
}
|
||||
|
||||
get selectLabelTemplate() {
|
||||
if (this.labelTemplate)
|
||||
return this.labelTemplate.template;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<ng-select [items]="data$ |async"
|
||||
[bindLabel]="bindLabel"
|
||||
[bindValue]="bindValue"
|
||||
[placeholder]="placeholder"
|
||||
autofocus
|
||||
[typeahead] = "searchInput$"
|
||||
[trackByFn]="trackFn"
|
||||
(change)="valueChanged($event)">
|
||||
<ng-container *ngIf="hasOptionTemplate">
|
||||
<ng-template ng-option-tmp let-item="item" let-index="index">
|
||||
<ng-container [ngTemplateOutlet]="selectOptionTemplate"
|
||||
[ngTemplateOutletContext]="{
|
||||
$implicit: item,
|
||||
index: index
|
||||
}"></ng-container>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="hasLabelTemplate">
|
||||
<ng-template ng-label-tmp let-item="item" let-index="index">
|
||||
<ng-container [ngTemplateOutlet]="selectLabelTemplate"
|
||||
[ngTemplateOutletContext]="{
|
||||
$implicit: item,
|
||||
index: index
|
||||
}"></ng-container>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="hasNotFoundTemplate">
|
||||
<ng-template ng-notfound-tmp let-searchTerm="searchTerm">
|
||||
<ng-container [ngTemplateOutlet]="selectNotFoundTemplate"
|
||||
[ngTemplateOutletContext]="{
|
||||
$implicit: searchTerm
|
||||
}"></ng-container>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ng-select>
|
||||
<ng-container *ngIf="footerTemplate">
|
||||
<ng-container [ngTemplateOutlet]="footerTemplate.template" [ngTemplateOutletContext]="{ $implicit: selectedModel }">
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ContentChild,
|
||||
EventEmitter,
|
||||
forwardRef,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import {SelectLabelTemplateDirective} from '../directives/select-label-template.directive';
|
||||
import {IDataSource, ISimpleFilter, ISort} from '@poweredsoft/data';
|
||||
import {Observable, Subject, Subscription} from 'rxjs';
|
||||
import {debounceTime, distinctUntilChanged, map} from 'rxjs/operators';
|
||||
import {NgSelectComponent as SelectComponent} from '@ng-select/ng-select';
|
||||
import {NG_VALUE_ACCESSOR} from '@angular/forms';
|
||||
import {SelectOptionTemplateDirective} from '../directives/select-option-template.directive';
|
||||
import {NotFoundTemplateDirective} from '../directives/not-found-template.directive';
|
||||
import {SelectFooterTemplateDirective} from '../directives/select-footer-template.directive';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ng-select-ds',
|
||||
templateUrl: './ng-select.component.html',
|
||||
providers: [{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => NgSelectComponent),
|
||||
multi: true
|
||||
}],
|
||||
styleUrls: ['./ng-select.component.scss']
|
||||
})
|
||||
export class NgSelectComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(SelectComponent, { static: true }) selectComponent: SelectComponent;
|
||||
|
||||
@ContentChild(SelectOptionTemplateDirective) optionTemplate: SelectOptionTemplateDirective;
|
||||
@ContentChild(SelectLabelTemplateDirective) labelTemplate: SelectLabelTemplateDirective;
|
||||
@ContentChild(NotFoundTemplateDirective) notFoundTemplate: NotFoundTemplateDirective;
|
||||
@ContentChild(SelectFooterTemplateDirective) footerTemplate: SelectFooterTemplateDirective;
|
||||
|
||||
@Input() dataSource: IDataSource<any>;
|
||||
@Input() searchPath: string | string[];
|
||||
@Input() searchType: string;
|
||||
@Input() sortingPath: string | string[];
|
||||
@Input() serverFiltering: boolean;
|
||||
@Input() bindLabel: string;
|
||||
@Input() bindValue: string;
|
||||
@Input() placeholder: string;
|
||||
|
||||
@Output('change') changeEvent = new EventEmitter();
|
||||
|
||||
trackFn: (item: any) => any;
|
||||
data$: Observable<any[]>;
|
||||
isLoading: boolean = false;
|
||||
searchInput$ = new Subject<string>();
|
||||
|
||||
private _loadingSubscription: Subscription;
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
this.trackFn = this.trackBy.bind(this);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dataFetching();
|
||||
this.detectLoading();
|
||||
|
||||
if (this.serverFiltering) {
|
||||
this.searchOnServer();
|
||||
} else {
|
||||
this.refreshDataSource();
|
||||
}
|
||||
}
|
||||
|
||||
valueChanged(event) {
|
||||
this.changeEvent.emit(event);
|
||||
}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.selectComponent.writeValue(obj);
|
||||
}
|
||||
registerOnChange(fn: any): void {
|
||||
this.selectComponent.registerOnChange(fn);
|
||||
}
|
||||
registerOnTouched(fn: any): void {
|
||||
this.selectComponent.registerOnTouched(fn);
|
||||
}
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
if (this.selectComponent.setDisabledState)
|
||||
this.selectComponent.setDisabledState(isDisabled);
|
||||
}
|
||||
|
||||
trackBy(item: any) {
|
||||
return this.dataSource.resolveIdField(item);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._loadingSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
dataFetching() {
|
||||
this.data$ = this.dataSource.data$.pipe(
|
||||
map(t => {
|
||||
if (t == null)
|
||||
return [];
|
||||
return t.data;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
detectLoading() {
|
||||
this._loadingSubscription = this.dataSource.loading$.subscribe(loading => {
|
||||
this.isLoading = loading;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
searchOnServer() {
|
||||
this.searchInput$.pipe(
|
||||
distinctUntilChanged(), // emit the difference from previous input
|
||||
debounceTime(500) // this is for delaying searching speed
|
||||
).subscribe(searchTerm => this.refreshDataSource(searchTerm, 1, 100)); // page: 1, pageSize: 50
|
||||
|
||||
this.refreshDataSource(null, 1, 100); //send the query to server to sorting & filtering by default
|
||||
}
|
||||
|
||||
get selectedModel() {
|
||||
return this.selectComponent.hasValue ? this.selectComponent.selectedItems[0].value : null;
|
||||
}
|
||||
|
||||
refreshDataSource(searchTerm: any = null, page: number = null, pageSize: number = null) {
|
||||
let searchfilters: ISimpleFilter[] = [];
|
||||
if (searchTerm == "")
|
||||
searchTerm = null;
|
||||
|
||||
if (searchTerm) {
|
||||
if (this.searchPath) {
|
||||
if (Array.isArray(this.searchPath)) {
|
||||
searchfilters = this.searchPath.map(path => {
|
||||
return <ISimpleFilter>{
|
||||
path: path,
|
||||
type: 'Contains',
|
||||
value: searchTerm,
|
||||
and: false
|
||||
}
|
||||
});
|
||||
} else {
|
||||
searchfilters = [<ISimpleFilter>{
|
||||
path: this.searchPath,
|
||||
type: 'Contains', // Default: Contains
|
||||
value: searchTerm
|
||||
}]
|
||||
}
|
||||
} else {
|
||||
searchfilters = [<ISimpleFilter>{
|
||||
path: this.bindLabel,
|
||||
type: 'Contains', // Default: Contains
|
||||
value: searchTerm
|
||||
}]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let sorts: ISort[];
|
||||
if (this.sortingPath && Array.isArray(this.sortingPath)) {
|
||||
sorts = this.sortingPath.map(sortPath => (<ISort>{
|
||||
path: sortPath,
|
||||
ascending: true
|
||||
}));
|
||||
} else {
|
||||
sorts = [<ISort>{ path: this.sortingPath || this.bindLabel, ascending: true }];
|
||||
}
|
||||
|
||||
|
||||
this.dataSource.query({
|
||||
page: page,
|
||||
pageSize: pageSize,
|
||||
filters: searchfilters,
|
||||
sorts: sorts
|
||||
});
|
||||
}
|
||||
|
||||
get hasOptionTemplate() {
|
||||
return this.optionTemplate ? true : false;
|
||||
}
|
||||
|
||||
get selectOptionTemplate() {
|
||||
if (this.optionTemplate)
|
||||
return this.optionTemplate.template;
|
||||
return null;
|
||||
}
|
||||
|
||||
get hasLabelTemplate() {
|
||||
return this.labelTemplate ? true : false;
|
||||
}
|
||||
|
||||
get selectLabelTemplate() {
|
||||
if (this.labelTemplate)
|
||||
return this.labelTemplate.template;
|
||||
return null;
|
||||
}
|
||||
|
||||
get hasNotFoundTemplate() {
|
||||
return this.notFoundTemplate ? true : false;
|
||||
}
|
||||
|
||||
get selectNotFoundTemplate() {
|
||||
if (this.notFoundTemplate)
|
||||
return this.notFoundTemplate.template;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {NgSelectComponent} from './ng-select/ng-select.component';
|
||||
import {MultiSelectComponent} from './multi-select/multi-select.component';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {NgSelectModule} from '@ng-select/ng-select';
|
||||
import {SelectLabelTemplateDirective} from './directives/select-label-template.directive';
|
||||
import {SelectOptionTemplateDirective} from './directives/select-option-template.directive';
|
||||
import {NotFoundTemplateDirective} from './directives/not-found-template.directive';
|
||||
import {SelectFooterTemplateDirective} from './directives/select-footer-template.directive';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [NgSelectComponent, MultiSelectComponent, SelectLabelTemplateDirective, SelectOptionTemplateDirective, NotFoundTemplateDirective, SelectFooterTemplateDirective],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
NgSelectModule,
|
||||
],
|
||||
exports: [
|
||||
NgSelectComponent,
|
||||
MultiSelectComponent,
|
||||
SelectLabelTemplateDirective,
|
||||
SelectOptionTemplateDirective,
|
||||
SelectFooterTemplateDirective,
|
||||
NotFoundTemplateDirective
|
||||
]
|
||||
})
|
||||
export class NgxDataNgSelectUIModule { }
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Public API Surface of ng-select
|
||||
*/
|
||||
|
||||
export * from './lib/ng-select-ds/ngx-data-ng-select-ui.module';
|
||||
export * from './lib/ng-select-ds/ng-select/ng-select.component';
|
||||
export * from './lib/ng-select-ds/multi-select/multi-select.component';
|
||||
export * from './lib/ng-select-ds/directives/select-label-template.directive';
|
||||
export * from './lib/ng-select-ds/directives/select-option-template.directive';
|
||||
export * from './lib/ng-select-ds/directives/select-footer-template.directive';
|
||||
export * from './lib/ng-select-ds/directives/not-found-template.directive';
|
||||
@@ -0,0 +1,26 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone';
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(path: string, deep?: boolean, filter?: RegExp): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
Reference in New Issue
Block a user