Commit 7e66cb938cb88dbd4f4369f540c101f6d1c584fc
1 parent
b0c5479c
UI: Model improvement, new entity table methods
Showing
6 changed files
with
119 additions
and
14 deletions
@@ -28,7 +28,7 @@ import { | @@ -28,7 +28,7 @@ import { | ||
28 | MissingTranslationHandler, | 28 | MissingTranslationHandler, |
29 | TranslateCompiler, | 29 | TranslateCompiler, |
30 | TranslateLoader, | 30 | TranslateLoader, |
31 | - TranslateModule | 31 | + TranslateModule, TranslateParser |
32 | } from '@ngx-translate/core'; | 32 | } from '@ngx-translate/core'; |
33 | import { TranslateHttpLoader } from '@ngx-translate/http-loader'; | 33 | import { TranslateHttpLoader } from '@ngx-translate/http-loader'; |
34 | import { TbMissingTranslationHandler } from './translate/missing-translate-handler'; | 34 | import { TbMissingTranslationHandler } from './translate/missing-translate-handler'; |
@@ -39,6 +39,7 @@ import { FlexLayoutModule } from '@angular/flex-layout'; | @@ -39,6 +39,7 @@ import { FlexLayoutModule } from '@angular/flex-layout'; | ||
39 | import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; | 39 | import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; |
40 | import { WINDOW_PROVIDERS } from '@core/services/window.service'; | 40 | import { WINDOW_PROVIDERS } from '@core/services/window.service'; |
41 | import { HotkeyModule } from 'angular2-hotkeys'; | 41 | import { HotkeyModule } from 'angular2-hotkeys'; |
42 | +import { TranslateDefaultParser } from '@core/translate/translate-default-parser'; | ||
42 | 43 | ||
43 | export function HttpLoaderFactory(http: HttpClient) { | 44 | export function HttpLoaderFactory(http: HttpClient) { |
44 | return new TranslateHttpLoader(http, './assets/locale/locale.constant-', '.json'); | 45 | return new TranslateHttpLoader(http, './assets/locale/locale.constant-', '.json'); |
@@ -67,6 +68,10 @@ export function HttpLoaderFactory(http: HttpClient) { | @@ -67,6 +68,10 @@ export function HttpLoaderFactory(http: HttpClient) { | ||
67 | compiler: { | 68 | compiler: { |
68 | provide: TranslateCompiler, | 69 | provide: TranslateCompiler, |
69 | useClass: TranslateDefaultCompiler | 70 | useClass: TranslateDefaultCompiler |
71 | + }, | ||
72 | + parser: { | ||
73 | + provide: TranslateParser, | ||
74 | + useClass: TranslateDefaultParser | ||
70 | } | 75 | } |
71 | }), | 76 | }), |
72 | HotkeyModule.forRoot(), | 77 | HotkeyModule.forRoot(), |
1 | +/// | ||
2 | +/// Copyright © 2016-2020 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Injectable } from '@angular/core'; | ||
18 | +import { TranslateParser } from '@ngx-translate/core'; | ||
19 | +import { isDefinedAndNotNull } from '@core/utils'; | ||
20 | + | ||
21 | +@Injectable({ providedIn: 'root' }) | ||
22 | +export class TranslateDefaultParser extends TranslateParser { | ||
23 | + templateMatcher: RegExp = /{{\s?([^{}\s]*)\s?}}/g; | ||
24 | + | ||
25 | + // tslint:disable-next-line:ban-types | ||
26 | + public interpolate(expr: string | Function, params?: any): string { | ||
27 | + let result: string; | ||
28 | + | ||
29 | + if (typeof expr === 'string') { | ||
30 | + result = this.interpolateString(expr, params); | ||
31 | + } else if (typeof expr === 'function') { | ||
32 | + result = this.interpolateFunction(expr, params); | ||
33 | + if (typeof result === 'string') { | ||
34 | + result = this.interpolateString(result, params); | ||
35 | + } | ||
36 | + } else { | ||
37 | + result = expr as string; | ||
38 | + } | ||
39 | + | ||
40 | + return result; | ||
41 | + } | ||
42 | + | ||
43 | + getValue(target: any, key: string): any { | ||
44 | + const keys = typeof key === 'string' ? key.split('.') : [key]; | ||
45 | + key = ''; | ||
46 | + do { | ||
47 | + key += keys.shift(); | ||
48 | + if (isDefinedAndNotNull(target) && isDefinedAndNotNull(target[key]) && (typeof target[key] === 'object' || !keys.length)) { | ||
49 | + target = target[key]; | ||
50 | + key = ''; | ||
51 | + } else if (!keys.length) { | ||
52 | + target = undefined; | ||
53 | + } else { | ||
54 | + key += '.'; | ||
55 | + } | ||
56 | + } while (keys.length); | ||
57 | + | ||
58 | + return target; | ||
59 | + } | ||
60 | + | ||
61 | + // tslint:disable-next-line:ban-types | ||
62 | + private interpolateFunction(fn: Function, params?: any) { | ||
63 | + return fn(params); | ||
64 | + } | ||
65 | + | ||
66 | + private interpolateString(expr: string, params?: any) { | ||
67 | + if (!params) { | ||
68 | + return expr; | ||
69 | + } | ||
70 | + | ||
71 | + return expr.replace(this.templateMatcher, (substring: string, b: string) => { | ||
72 | + const r = this.getValue(params, b); | ||
73 | + return isDefinedAndNotNull(r) ? r : substring; | ||
74 | + }); | ||
75 | + } | ||
76 | +} |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | import { | 17 | import { |
18 | AfterViewInit, | 18 | AfterViewInit, |
19 | - ChangeDetectionStrategy, | 19 | + ChangeDetectionStrategy, ChangeDetectorRef, |
20 | Component, | 20 | Component, |
21 | ComponentFactoryResolver, | 21 | ComponentFactoryResolver, |
22 | ElementRef, EventEmitter, | 22 | ElementRef, EventEmitter, |
@@ -115,6 +115,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -115,6 +115,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
115 | public dialog: MatDialog, | 115 | public dialog: MatDialog, |
116 | private dialogService: DialogService, | 116 | private dialogService: DialogService, |
117 | private domSanitizer: DomSanitizer, | 117 | private domSanitizer: DomSanitizer, |
118 | + private cd: ChangeDetectorRef, | ||
118 | private componentFactoryResolver: ComponentFactoryResolver) { | 119 | private componentFactoryResolver: ComponentFactoryResolver) { |
119 | super(store); | 120 | super(store); |
120 | } | 121 | } |
@@ -204,9 +205,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -204,9 +205,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
204 | this.pageLink = new PageLink(10, 0, null, sortOrder); | 205 | this.pageLink = new PageLink(10, 0, null, sortOrder); |
205 | } | 206 | } |
206 | this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : MAX_SAFE_PAGE_SIZE; | 207 | this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : MAX_SAFE_PAGE_SIZE; |
207 | - this.dataSource = this.entitiesTableConfig.dataSource(() => { | ||
208 | - this.dataLoaded(); | ||
209 | - }); | 208 | + this.dataSource = this.entitiesTableConfig.dataSource(this.dataLoaded.bind(this)); |
210 | if (this.entitiesTableConfig.onLoadAction) { | 209 | if (this.entitiesTableConfig.onLoadAction) { |
211 | this.entitiesTableConfig.onLoadAction(this.route); | 210 | this.entitiesTableConfig.onLoadAction(this.route); |
212 | } | 211 | } |
@@ -262,6 +261,11 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -262,6 +261,11 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
262 | return this.entitiesTableConfig.addEnabled; | 261 | return this.entitiesTableConfig.addEnabled; |
263 | } | 262 | } |
264 | 263 | ||
264 | + clearSelection() { | ||
265 | + this.dataSource.selection.clear(); | ||
266 | + this.cd.detectChanges(); | ||
267 | + } | ||
268 | + | ||
265 | updateData(closeDetails: boolean = true) { | 269 | updateData(closeDetails: boolean = true) { |
266 | if (closeDetails) { | 270 | if (closeDetails) { |
267 | this.isDetailsOpen = false; | 271 | this.isDetailsOpen = false; |
@@ -294,11 +298,15 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -294,11 +298,15 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
294 | this.dataSource.loadEntities(this.pageLink); | 298 | this.dataSource.loadEntities(this.pageLink); |
295 | } | 299 | } |
296 | 300 | ||
297 | - private dataLoaded() { | ||
298 | - this.headerCellStyleCache.length = 0; | ||
299 | - this.cellContentCache.length = 0; | ||
300 | - this.cellTooltipCache.length = 0; | ||
301 | - this.cellStyleCache.length = 0; | 301 | + private dataLoaded(col?: number, row?: number) { |
302 | + if (isFinite(col) && isFinite(row)) { | ||
303 | + this.clearCellCache(col, row); | ||
304 | + } else { | ||
305 | + this.headerCellStyleCache.length = 0; | ||
306 | + this.cellContentCache.length = 0; | ||
307 | + this.cellTooltipCache.length = 0; | ||
308 | + this.cellStyleCache.length = 0; | ||
309 | + } | ||
302 | } | 310 | } |
303 | 311 | ||
304 | onRowClick($event: Event, entity) { | 312 | onRowClick($event: Event, entity) { |
@@ -485,6 +493,13 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -485,6 +493,13 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
485 | return res; | 493 | return res; |
486 | } | 494 | } |
487 | 495 | ||
496 | + clearCellCache(col: number, row: number) { | ||
497 | + const index = row * this.entitiesTableConfig.columns.length + col; | ||
498 | + this.cellContentCache[index] = undefined; | ||
499 | + this.cellTooltipCache[index] = undefined; | ||
500 | + this.cellStyleCache[index] = undefined; | ||
501 | + } | ||
502 | + | ||
488 | cellContent(entity: BaseData<HasId>, column: EntityColumn<BaseData<HasId>>, row: number) { | 503 | cellContent(entity: BaseData<HasId>, column: EntityColumn<BaseData<HasId>>, row: number) { |
489 | if (column instanceof EntityTableColumn) { | 504 | if (column instanceof EntityTableColumn) { |
490 | const col = this.entitiesTableConfig.columns.indexOf(column); | 505 | const col = this.entitiesTableConfig.columns.indexOf(column); |
@@ -37,7 +37,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink = | @@ -37,7 +37,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink = | ||
37 | 37 | ||
38 | constructor(private fetchFunction: EntitiesFetchFunction<T, P>, | 38 | constructor(private fetchFunction: EntitiesFetchFunction<T, P>, |
39 | protected selectionEnabledFunction: EntityBooleanFunction<T>, | 39 | protected selectionEnabledFunction: EntityBooleanFunction<T>, |
40 | - protected dataLoadedFunction: () => void) {} | 40 | + protected dataLoadedFunction: (col?: number, row?: number) => void) {} |
41 | 41 | ||
42 | connect(collectionViewer: CollectionViewer): Observable<T[] | ReadonlyArray<T>> { | 42 | connect(collectionViewer: CollectionViewer): Observable<T[] | ReadonlyArray<T>> { |
43 | return this.entitiesSubject.asObservable(); | 43 | return this.entitiesSubject.asObservable(); |
@@ -50,7 +50,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink = | @@ -50,7 +50,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink = | ||
50 | 50 | ||
51 | reset() { | 51 | reset() { |
52 | const pageData = emptyPageData<T>(); | 52 | const pageData = emptyPageData<T>(); |
53 | - this.entitiesSubject.next(pageData.data); | 53 | + this.onEntities(pageData.data); |
54 | this.pageDataSubject.next(pageData); | 54 | this.pageDataSubject.next(pageData); |
55 | this.dataLoadedFunction(); | 55 | this.dataLoadedFunction(); |
56 | } | 56 | } |
@@ -64,7 +64,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink = | @@ -64,7 +64,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink = | ||
64 | catchError(() => of(emptyPageData<T>())), | 64 | catchError(() => of(emptyPageData<T>())), |
65 | ).subscribe( | 65 | ).subscribe( |
66 | (pageData) => { | 66 | (pageData) => { |
67 | - this.entitiesSubject.next(pageData.data); | 67 | + this.onEntities(pageData.data); |
68 | this.pageDataSubject.next(pageData); | 68 | this.pageDataSubject.next(pageData); |
69 | result.next(pageData); | 69 | result.next(pageData); |
70 | this.dataLoadedFunction(); | 70 | this.dataLoadedFunction(); |
@@ -73,6 +73,10 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink = | @@ -73,6 +73,10 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink = | ||
73 | return result; | 73 | return result; |
74 | } | 74 | } |
75 | 75 | ||
76 | + protected onEntities(entities: T[]) { | ||
77 | + this.entitiesSubject.next(entities); | ||
78 | + } | ||
79 | + | ||
76 | isAllSelected(): Observable<boolean> { | 80 | isAllSelected(): Observable<boolean> { |
77 | const numSelected = this.selection.selected.length; | 81 | const numSelected = this.selection.selected.length; |
78 | return this.entitiesSubject.pipe( | 82 | return this.entitiesSubject.pipe( |
@@ -157,7 +157,8 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P | @@ -157,7 +157,8 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P | ||
157 | addActionDescriptors: Array<HeaderActionDescriptor> = []; | 157 | addActionDescriptors: Array<HeaderActionDescriptor> = []; |
158 | headerComponent: Type<EntityTableHeaderComponent<T, P, L>>; | 158 | headerComponent: Type<EntityTableHeaderComponent<T, P, L>>; |
159 | addEntity: CreateEntityOperation<T> = null; | 159 | addEntity: CreateEntityOperation<T> = null; |
160 | - dataSource: (dataLoadedFunction: () => void) => EntitiesDataSource<L> = (dataLoadedFunction: () => void) => { | 160 | + dataSource: (dataLoadedFunction: (col?: number, row?: number) => void) |
161 | + => EntitiesDataSource<L> = (dataLoadedFunction: (col?: number, row?: number) => void) => { | ||
161 | return new EntitiesDataSource(this.entitiesFetchFunction, this.entitySelectionEnabled, dataLoadedFunction); | 162 | return new EntitiesDataSource(this.entitiesFetchFunction, this.entitySelectionEnabled, dataLoadedFunction); |
162 | }; | 163 | }; |
163 | detailsReadonly: EntityBooleanFunction<T> = () => false; | 164 | detailsReadonly: EntityBooleanFunction<T> = () => false; |
@@ -279,6 +279,10 @@ export class TelemetrySubscriber { | @@ -279,6 +279,10 @@ export class TelemetrySubscriber { | ||
279 | 279 | ||
280 | public unsubscribe() { | 280 | public unsubscribe() { |
281 | this.telemetryService.unsubscribe(this); | 281 | this.telemetryService.unsubscribe(this); |
282 | + this.complete(); | ||
283 | + } | ||
284 | + | ||
285 | + public complete() { | ||
282 | this.dataSubject.complete(); | 286 | this.dataSubject.complete(); |
283 | this.reconnectSubject.complete(); | 287 | this.reconnectSubject.complete(); |
284 | } | 288 | } |