Commit 7e66cb938cb88dbd4f4369f540c101f6d1c584fc

Authored by Igor Kulikov
1 parent b0c5479c

UI: Model improvement, new entity table methods

... ... @@ -28,7 +28,7 @@ import {
28 28 MissingTranslationHandler,
29 29 TranslateCompiler,
30 30 TranslateLoader,
31   - TranslateModule
  31 + TranslateModule, TranslateParser
32 32 } from '@ngx-translate/core';
33 33 import { TranslateHttpLoader } from '@ngx-translate/http-loader';
34 34 import { TbMissingTranslationHandler } from './translate/missing-translate-handler';
... ... @@ -39,6 +39,7 @@ import { FlexLayoutModule } from '@angular/flex-layout';
39 39 import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler';
40 40 import { WINDOW_PROVIDERS } from '@core/services/window.service';
41 41 import { HotkeyModule } from 'angular2-hotkeys';
  42 +import { TranslateDefaultParser } from '@core/translate/translate-default-parser';
42 43
43 44 export function HttpLoaderFactory(http: HttpClient) {
44 45 return new TranslateHttpLoader(http, './assets/locale/locale.constant-', '.json');
... ... @@ -67,6 +68,10 @@ export function HttpLoaderFactory(http: HttpClient) {
67 68 compiler: {
68 69 provide: TranslateCompiler,
69 70 useClass: TranslateDefaultCompiler
  71 + },
  72 + parser: {
  73 + provide: TranslateParser,
  74 + useClass: TranslateDefaultParser
70 75 }
71 76 }),
72 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 16
17 17 import {
18 18 AfterViewInit,
19   - ChangeDetectionStrategy,
  19 + ChangeDetectionStrategy, ChangeDetectorRef,
20 20 Component,
21 21 ComponentFactoryResolver,
22 22 ElementRef, EventEmitter,
... ... @@ -115,6 +115,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
115 115 public dialog: MatDialog,
116 116 private dialogService: DialogService,
117 117 private domSanitizer: DomSanitizer,
  118 + private cd: ChangeDetectorRef,
118 119 private componentFactoryResolver: ComponentFactoryResolver) {
119 120 super(store);
120 121 }
... ... @@ -204,9 +205,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
204 205 this.pageLink = new PageLink(10, 0, null, sortOrder);
205 206 }
206 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 209 if (this.entitiesTableConfig.onLoadAction) {
211 210 this.entitiesTableConfig.onLoadAction(this.route);
212 211 }
... ... @@ -262,6 +261,11 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
262 261 return this.entitiesTableConfig.addEnabled;
263 262 }
264 263
  264 + clearSelection() {
  265 + this.dataSource.selection.clear();
  266 + this.cd.detectChanges();
  267 + }
  268 +
265 269 updateData(closeDetails: boolean = true) {
266 270 if (closeDetails) {
267 271 this.isDetailsOpen = false;
... ... @@ -294,11 +298,15 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
294 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 312 onRowClick($event: Event, entity) {
... ... @@ -485,6 +493,13 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
485 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 503 cellContent(entity: BaseData<HasId>, column: EntityColumn<BaseData<HasId>>, row: number) {
489 504 if (column instanceof EntityTableColumn) {
490 505 const col = this.entitiesTableConfig.columns.indexOf(column);
... ...
... ... @@ -37,7 +37,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink =
37 37
38 38 constructor(private fetchFunction: EntitiesFetchFunction<T, P>,
39 39 protected selectionEnabledFunction: EntityBooleanFunction<T>,
40   - protected dataLoadedFunction: () => void) {}
  40 + protected dataLoadedFunction: (col?: number, row?: number) => void) {}
41 41
42 42 connect(collectionViewer: CollectionViewer): Observable<T[] | ReadonlyArray<T>> {
43 43 return this.entitiesSubject.asObservable();
... ... @@ -50,7 +50,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink =
50 50
51 51 reset() {
52 52 const pageData = emptyPageData<T>();
53   - this.entitiesSubject.next(pageData.data);
  53 + this.onEntities(pageData.data);
54 54 this.pageDataSubject.next(pageData);
55 55 this.dataLoadedFunction();
56 56 }
... ... @@ -64,7 +64,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink =
64 64 catchError(() => of(emptyPageData<T>())),
65 65 ).subscribe(
66 66 (pageData) => {
67   - this.entitiesSubject.next(pageData.data);
  67 + this.onEntities(pageData.data);
68 68 this.pageDataSubject.next(pageData);
69 69 result.next(pageData);
70 70 this.dataLoadedFunction();
... ... @@ -73,6 +73,10 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink =
73 73 return result;
74 74 }
75 75
  76 + protected onEntities(entities: T[]) {
  77 + this.entitiesSubject.next(entities);
  78 + }
  79 +
76 80 isAllSelected(): Observable<boolean> {
77 81 const numSelected = this.selection.selected.length;
78 82 return this.entitiesSubject.pipe(
... ...
... ... @@ -157,7 +157,8 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
157 157 addActionDescriptors: Array<HeaderActionDescriptor> = [];
158 158 headerComponent: Type<EntityTableHeaderComponent<T, P, L>>;
159 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 162 return new EntitiesDataSource(this.entitiesFetchFunction, this.entitySelectionEnabled, dataLoadedFunction);
162 163 };
163 164 detailsReadonly: EntityBooleanFunction<T> = () => false;
... ...
... ... @@ -279,6 +279,10 @@ export class TelemetrySubscriber {
279 279
280 280 public unsubscribe() {
281 281 this.telemetryService.unsubscribe(this);
  282 + this.complete();
  283 + }
  284 +
  285 + public complete() {
282 286 this.dataSubject.complete();
283 287 this.reconnectSubject.complete();
284 288 }
... ...