From 2ac158d3672644c191c56eef1dc13fc860111794 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 10 Sep 2019 13:51:50 +0300 Subject: [PATCH] feature(core): add abpFor directive support to add layout type from routing module --- .../components/dynamic-layout.component.ts | 32 ++-- .../packages/core/src/lib/core.module.ts | 3 + .../core/src/lib/directives/for.directive.ts | 174 ++++++++++++++++++ .../packages/core/src/lib/directives/index.ts | 1 + .../packages/core/src/lib/enums/common.ts | 1 + .../packages/core/src/lib/models/common.ts | 2 +- templates/app/angular/src/app/app.module.ts | 11 +- 7 files changed, 204 insertions(+), 20 deletions(-) create mode 100644 npm/ng-packs/packages/core/src/lib/directives/for.directive.ts diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index 517e1974ad..d89d486c57 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -1,12 +1,13 @@ -import { Component, OnDestroy, Type } from '@angular/core'; -import { NavigationEnd, Router, UrlSegment } from '@angular/router'; +import { Component, Input, OnDestroy, Type } from '@angular/core'; +import { ActivatedRoute, NavigationEnd, Router, UrlSegment } from '@angular/router'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; -import { eLayoutType } from '../enums'; -import { ABP, Config } from '../models'; -import { ConfigState } from '../states'; -import { takeUntilDestroy } from '../utils'; import snq from 'snq'; +import { eLayoutType } from '../enums/common'; +import { Config } from '../models/config'; +import { ABP } from '../models/common'; +import { ConfigState } from '../states/config.state'; +import { takeUntilDestroy } from '../utils/rxjs-utils'; @Component({ selector: 'abp-dynamic-layout', @@ -23,16 +24,23 @@ export class DynamicLayoutComponent implements OnDestroy { layout: Type; - constructor(private router: Router, private store: Store) { + constructor(private router: Router, private route: ActivatedRoute, private store: Store) { + const { + requirements: { layouts }, + routes, + } = this.store.selectSnapshot(ConfigState.getAll); + + if ((this.route.snapshot.data || {}).layout) { + this.layout = layouts + .filter(l => !!l) + .find(l => snq(() => l.type.toLowerCase().indexOf(this.route.snapshot.data.layout), -1) > -1); + } + this.router.events.pipe(takeUntilDestroy(this)).subscribe(event => { if (event instanceof NavigationEnd) { const { segments } = this.router.parseUrl(event.url).root.children.primary; - const { - requirements: { layouts }, - routes, - } = this.store.selectSnapshot(ConfigState.getAll); - const layout = findLayout(segments, routes); + const layout = (this.route.snapshot.data || {}).layout || findLayout(segments, routes); this.layout = layouts.filter(l => !!l).find(l => snq(() => l.type.toLowerCase().indexOf(layout), -1) > -1); } diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index 6e801ffd83..c9c6ce9761 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -24,6 +24,7 @@ import { ProfileState } from './states/profile.state'; import { SessionState } from './states/session.state'; import { getInitialData, localeInitializer } from './utils/initial-utils'; import { ConfigPlugin, NGXS_CONFIG_PLUGIN_OPTIONS } from './plugins/config/config.plugin'; +import { ForDirective } from './directives/for.directive'; @NgModule({ imports: [ @@ -41,6 +42,7 @@ import { ConfigPlugin, NGXS_CONFIG_PLUGIN_OPTIONS } from './plugins/config/confi DynamicLayoutComponent, AutofocusDirective, EllipsisDirective, + ForDirective, FormSubmitDirective, LocalizationPipe, PermissionDirective, @@ -58,6 +60,7 @@ import { ConfigPlugin, NGXS_CONFIG_PLUGIN_OPTIONS } from './plugins/config/confi DynamicLayoutComponent, AutofocusDirective, EllipsisDirective, + ForDirective, FormSubmitDirective, LocalizationPipe, PermissionDirective, diff --git a/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts new file mode 100644 index 0000000000..24769b325d --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts @@ -0,0 +1,174 @@ +import { + Directive, + EmbeddedViewRef, + Input, + IterableChangeRecord, + IterableChanges, + IterableDiffer, + IterableDiffers, + OnChanges, + TemplateRef, + TrackByFunction, + ViewContainerRef, +} from '@angular/core'; +import compare from 'just-compare'; + +export type CompareFn = (value: T, comparison: T) => boolean; + +class AbpForContext { + constructor(public $implicit: any, public index: number, public count: number, public list: any[]) {} +} + +class RecordView { + constructor(public record: IterableChangeRecord, public view: EmbeddedViewRef) {} +} + +@Directive({ + selector: '[abpFor]', +}) +export class ForDirective implements OnChanges { + @Input('abpForOf') + items: any[]; + + @Input('abpForOrderBy') + orderBy: string; + + @Input('abpForOrderDir') + orderDir: 'ASC' | 'DESC'; + + @Input('abpForFilterBy') + filterBy: string; + + @Input('abpForFilterVal') + filterVal: any; + + @Input('abpForTrackBy') + trackBy; + + @Input('abpForCompareBy') + compareBy: CompareFn; + + @Input('abpForEmptyRef') + emptyRef: TemplateRef; + + private differ: IterableDiffer; + + private isShowEmptyRef: boolean; + + get compareFn(): CompareFn { + return this.compareBy || compare; + } + + get trackByFn(): TrackByFunction { + return this.trackBy || ((index: number, item: any) => (item as any).id || index); + } + + constructor( + private tempRef: TemplateRef, + private vcRef: ViewContainerRef, + private differs: IterableDiffers, + ) {} + + private iterateOverAppliedOperations(changes: IterableChanges) { + const rw: RecordView[] = []; + + changes.forEachOperation((record: IterableChangeRecord, previousIndex: number, currentIndex: number) => { + if (record.previousIndex == null) { + const view = this.vcRef.createEmbeddedView( + this.tempRef, + new AbpForContext(null, -1, -1, this.items), + currentIndex, + ); + + rw.push(new RecordView(record, view)); + } else if (currentIndex == null) { + this.vcRef.remove(previousIndex); + } else { + const view = this.vcRef.get(previousIndex); + this.vcRef.move(view, currentIndex); + + rw.push(new RecordView(record, view as EmbeddedViewRef)); + } + }); + + for (let i = 0, l = rw.length; i < l; i++) { + rw[i].view.context.$implicit = rw[i].record.item; + } + } + + private iterateOverAttachedViews(changes: IterableChanges) { + for (let i = 0, l = this.vcRef.length; i < l; i++) { + const viewRef = this.vcRef.get(i) as EmbeddedViewRef; + viewRef.context.index = i; + viewRef.context.count = l; + viewRef.context.list = this.items; + } + + changes.forEachIdentityChange((record: IterableChangeRecord) => { + const viewRef = this.vcRef.get(record.currentIndex) as EmbeddedViewRef; + viewRef.context.$implicit = record.item; + }); + } + + private projectItems(items: any[]): void { + if (!items.length && this.emptyRef) { + this.vcRef.createEmbeddedView(this.emptyRef).rootNodes; + this.isShowEmptyRef = true; + + return; + } + + if (this.emptyRef && this.isShowEmptyRef) { + this.vcRef.clear(); + this.isShowEmptyRef = false; + } + + if (!this.differ && items) { + this.differ = this.differs.find(items).create(this.trackByFn); + } + + if (this.differ) { + const changes = this.differ.diff(items); + + if (changes) { + this.iterateOverAppliedOperations(changes); + this.iterateOverAttachedViews(changes); + } + } + } + + private sortItems(items: any[]) { + if (this.orderBy) { + items.sort((a, b) => (a[this.orderBy] > b[this.orderBy] ? 1 : a[this.orderBy] < b[this.orderBy] ? -1 : 0)); + } else { + items.sort(); + } + } + + ngOnChanges() { + let items = [...this.items] as any[]; + if (!Array.isArray(items)) return; + + const compareFn = this.compareFn; + + if (typeof this.filterBy !== 'undefined') { + items = items.filter(item => compareFn(item[this.filterBy], this.filterVal)); + } + + switch (this.orderDir) { + case 'ASC': + this.sortItems(items); + this.projectItems(items); + break; + + case 'DESC': + this.sortItems(items); + items.reverse(); + this.projectItems(items); + break; + + default: + this.projectItems(items); + } + } +} diff --git a/npm/ng-packs/packages/core/src/lib/directives/index.ts b/npm/ng-packs/packages/core/src/lib/directives/index.ts index 336b6ff5ab..7edf1b5f76 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/index.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/index.ts @@ -1,5 +1,6 @@ export * from './autofocus.directive'; export * from './ellipsis.directive'; +export * from './for.directive'; export * from './form-submit.directive'; export * from './permission.directive'; export * from './visibility.directive'; diff --git a/npm/ng-packs/packages/core/src/lib/enums/common.ts b/npm/ng-packs/packages/core/src/lib/enums/common.ts index 08ddf05b6d..3b953b8ac7 100644 --- a/npm/ng-packs/packages/core/src/lib/enums/common.ts +++ b/npm/ng-packs/packages/core/src/lib/enums/common.ts @@ -2,4 +2,5 @@ export const enum eLayoutType { account = 'account', application = 'application', empty = 'empty', + setting = 'setting', } diff --git a/npm/ng-packs/packages/core/src/lib/models/common.ts b/npm/ng-packs/packages/core/src/lib/models/common.ts index 502363a323..a5089760f0 100644 --- a/npm/ng-packs/packages/core/src/lib/models/common.ts +++ b/npm/ng-packs/packages/core/src/lib/models/common.ts @@ -1,5 +1,5 @@ import { Config } from './config'; -import { eLayoutType } from '../enums'; +import { eLayoutType } from '../enums/common'; export namespace ABP { export interface Root { diff --git a/templates/app/angular/src/app/app.module.ts b/templates/app/angular/src/app/app.module.ts index 86b2d1b7be..7e942b2648 100644 --- a/templates/app/angular/src/app/app.module.ts +++ b/templates/app/angular/src/app/app.module.ts @@ -11,9 +11,9 @@ import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { SharedModule } from './shared/shared.module'; import { ThemeSharedModule } from '@abp/ng.theme.shared'; -import { RootAccountModule } from '@abp/ng.account'; -import { RootIdentityModule } from '@abp/ng.identity'; -import { RootTenantManagementModule } from '@abp/ng.tenant-management'; +import { AccountProviders } from '@abp/ng.account'; +import { IdentityProviders } from '@abp/ng.identity'; +import { TenantManagementProviders } from '@abp/ng.tenant-management'; @NgModule({ declarations: [AppComponent], @@ -29,15 +29,12 @@ import { RootTenantManagementModule } from '@abp/ng.tenant-management'; layouts: LAYOUTS, }, }), - RootAccountModule.forRoot({ redirectUrl: '/' }), - RootIdentityModule.forRoot(), - RootTenantManagementModule.forRoot(), OAuthModule.forRoot(), NgxsModule.forRoot([]), NgxsReduxDevtoolsPluginModule.forRoot({ disabled: environment.production }), ], - providers: [], + providers: [...AccountProviders({ redirectUrl: '/' }), ...IdentityProviders(), ...TenantManagementProviders()], bootstrap: [AppComponent], }) export class AppModule {}