Commit a0623e8e86064c592d141d2b90e2003727989bd3

Authored by Vladyslav_Prykhodko
1 parent c2c2f55d

UI: Add cache cellContent, cellStyle, rowStyle in entity tables widget

... ... @@ -528,7 +528,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
528 528 const col = this.entitiesTableConfig.columns.indexOf(column);
529 529 const index = row * this.entitiesTableConfig.columns.length + col;
530 530 let res = this.cellContentCache[index];
531   - if (!res) {
  531 + if (isUndefined(res)) {
532 532 res = this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key));
533 533 this.cellContentCache[index] = res;
534 534 }
... ...
... ... @@ -81,9 +81,9 @@
81 81 <mat-header-cell [ngStyle]="headerStyle(column)" *matHeaderCellDef mat-sort-header [disabled]="isSorting(column)">
82 82 {{ column.title }}
83 83 </mat-header-cell>
84   - <mat-cell *matCellDef="let alarm;"
85   - [innerHTML]="cellContent(alarm, column)"
86   - [ngStyle]="cellStyle(alarm, column)">
  84 + <mat-cell *matCellDef="let alarm; let row = index"
  85 + [innerHTML]="cellContent(alarm, column, row)"
  86 + [ngStyle]="cellStyle(alarm, column, row)">
87 87 </mat-cell>
88 88 </ng-container>
89 89 <ng-container matColumnDef="actions" [stickyEnd]="enableStickyAction">
... ... @@ -125,8 +125,8 @@
125 125 'mat-selected': alarmsDatasource.isSelected(alarm),
126 126 'tb-current-entity': alarmsDatasource.isCurrentAlarm(alarm),
127 127 'invisible': alarmsDatasource.dataLoading}"
128   - *matRowDef="let alarm; columns: displayedColumns;"
129   - [ngStyle]="rowStyle(alarm)"
  128 + *matRowDef="let alarm; columns: displayedColumns; let row = index"
  129 + [ngStyle]="rowStyle(alarm, row)"
130 130 (click)="onRowClick($event, alarm)"></mat-row>
131 131 </table>
132 132 <span [fxShow]="(alarmsDatasource.isEmpty() | async) && !alarmsDatasource.dataLoading"
... ...
... ... @@ -35,7 +35,15 @@ import { DataKey, WidgetActionDescriptor, WidgetConfig } from '@shared/models/wi
35 35 import { IWidgetSubscription } from '@core/api/widget-api.models';
36 36 import { UtilsService } from '@core/services/utils.service';
37 37 import { TranslateService } from '@ngx-translate/core';
38   -import { createLabelFromDatasource, deepClone, hashCode, isDefined, isNumber, isObject } from '@core/utils';
  38 +import {
  39 + createLabelFromDatasource,
  40 + deepClone,
  41 + hashCode,
  42 + isDefined,
  43 + isNumber,
  44 + isObject,
  45 + isUndefined
  46 +} from '@core/utils';
39 47 import cssjs from '@core/css/css';
40 48 import { sortItems } from '@shared/models/page/page-link';
41 49 import { Direction } from '@shared/models/page/sort-order';
... ... @@ -155,6 +163,10 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
155 163 public actionCellDescriptors: AlarmWidgetActionDescriptor[] = [];
156 164 public alarmsDatasource: AlarmsDatasource;
157 165
  166 + private cellContentCache: Array<any> = [];
  167 + private cellStyleCache: Array<any> = [];
  168 + private rowStyleCache: Array<any> = [];
  169 +
158 170 private settings: AlarmsTableWidgetSettings;
159 171 private widgetConfig: WidgetConfig;
160 172 private subscription: IWidgetSubscription;
... ... @@ -261,6 +273,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
261 273 public onDataUpdated() {
262 274 this.updateTitle(true);
263 275 this.alarmsDatasource.updateAlarms();
  276 + this.clearCache();
264 277 }
265 278
266 279 public pageLinkSortDirection(): SortDirection {
... ... @@ -484,6 +497,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
484 497 if (this.actionCellDescriptors.length) {
485 498 this.displayedColumns.push('actions');
486 499 }
  500 + this.clearCache();
487 501 }
488 502 } as DisplayColumnsPanelData
489 503 },
... ... @@ -609,91 +623,100 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
609 623 return widthStyle(columnWidth);
610 624 }
611 625
612   - public rowStyle(alarm: AlarmDataInfo): any {
613   - let style: any = {};
614   - if (alarm) {
615   - if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) {
  626 + public rowStyle(alarm: AlarmDataInfo, row: number): any {
  627 + let res = this.rowStyleCache[row];
  628 + if (!res) {
  629 + res = {};
  630 + if (alarm && this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) {
616 631 try {
617   - style = this.rowStylesInfo.rowStyleFunction(alarm, this.ctx);
618   - if (!isObject(style)) {
619   - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
  632 + res = this.rowStylesInfo.rowStyleFunction(alarm, this.ctx);
  633 + if (!isObject(res)) {
  634 + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`);
620 635 }
621   - if (Array.isArray(style)) {
  636 + if (Array.isArray(res)) {
622 637 throw new TypeError(`Array instead of style object`);
623 638 }
624 639 } catch (e) {
625   - style = {};
  640 + res = {};
626 641 console.warn(`Row style function in widget '${this.ctx.widgetTitle}' ` +
627 642 `returns '${e}'. Please check your row style function.`);
628 643 }
629   - } else {
630   - style = {};
631 644 }
  645 + this.rowStyleCache[row] = res;
632 646 }
633   - return style;
  647 + return res;
634 648 }
635 649
636   - public cellStyle(alarm: AlarmDataInfo, key: EntityColumn): any {
637   - let style: any = {};
638   - if (alarm && key) {
639   - const styleInfo = this.stylesInfo[key.def];
640   - const value = getAlarmValue(alarm, key);
641   - if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
642   - try {
643   - style = styleInfo.cellStyleFunction(value, alarm, this.ctx);
644   - if (!isObject(style)) {
645   - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
646   - }
647   - if (Array.isArray(style)) {
648   - throw new TypeError(`Array instead of style object`);
  650 + public cellStyle(alarm: AlarmDataInfo, key: EntityColumn, row: number): any {
  651 + const col = this.columns.indexOf(key);
  652 + const index = row * this.columns.length + col;
  653 + let res = this.cellStyleCache[index];
  654 + if (!res) {
  655 + res = {};
  656 + if (alarm && key) {
  657 + const styleInfo = this.stylesInfo[key.def];
  658 + const value = getAlarmValue(alarm, key);
  659 + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
  660 + try {
  661 + res = styleInfo.cellStyleFunction(value, alarm, this.ctx);
  662 + if (!isObject(res)) {
  663 + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`);
  664 + }
  665 + if (Array.isArray(res)) {
  666 + throw new TypeError(`Array instead of style object`);
  667 + }
  668 + } catch (e) {
  669 + res = {};
  670 + console.warn(`Cell style function for data key '${key.label}' in widget '${this.ctx.widgetTitle}' ` +
  671 + `returns '${e}'. Please check your cell style function.`);
649 672 }
650   - } catch (e) {
651   - style = {};
652   - console.warn(`Cell style function for data key '${key.label}' in widget '${this.ctx.widgetTitle}' ` +
653   - `returns '${e}'. Please check your cell style function.`);
  673 + } else {
  674 + res = this.defaultStyle(key, value);
654 675 }
655   - } else {
656   - style = this.defaultStyle(key, value);
657 676 }
  677 + this.cellStyleCache[index] = res;
658 678 }
659   - if (!style.width) {
  679 + if (!res.width) {
660 680 const columnWidth = this.columnWidth[key.def];
661   - style = {...style, ...widthStyle(columnWidth)};
  681 + res = Object.assign(res, widthStyle(columnWidth));
662 682 }
663   - return style;
  683 + return res;
664 684 }
665 685
666   - public cellContent(alarm: AlarmDataInfo, key: EntityColumn): SafeHtml {
667   - if (alarm && key) {
668   - const contentInfo = this.contentsInfo[key.def];
669   - const value = getAlarmValue(alarm, key);
670   - let content = '';
671   - if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
672   - try {
673   - content = contentInfo.cellContentFunction(value, alarm, this.ctx);
674   - } catch (e) {
675   - content = '' + value;
  686 + public cellContent(alarm: AlarmDataInfo, key: EntityColumn, row: number): SafeHtml {
  687 + const col = this.columns.indexOf(key);
  688 + const index = row * this.columns.length + col;
  689 + let res = this.cellContentCache[index];
  690 + if (isUndefined(res)) {
  691 + res = '';
  692 + if (alarm && key) {
  693 + const contentInfo = this.contentsInfo[key.def];
  694 + const value = getAlarmValue(alarm, key);
  695 + let content = '';
  696 + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
  697 + try {
  698 + content = contentInfo.cellContentFunction(value, alarm, this.ctx);
  699 + } catch (e) {
  700 + content = '' + value;
  701 + }
  702 + } else {
  703 + content = this.defaultContent(key, contentInfo, value);
676 704 }
677   - } else {
678   - content = this.defaultContent(key, contentInfo, value);
679   - }
680   -
681   - if (!isDefined(content)) {
682   - return '';
683 705
684   - } else {
685   - content = this.utils.customTranslation(content, content);
686   - switch (typeof content) {
687   - case 'string':
688   - return this.domSanitizer.bypassSecurityTrustHtml(content);
689   - default:
690   - return content;
  706 + if (isDefined(content)) {
  707 + content = this.utils.customTranslation(content, content);
  708 + switch (typeof content) {
  709 + case 'string':
  710 + res = this.domSanitizer.bypassSecurityTrustHtml(content);
  711 + break;
  712 + default:
  713 + res = content;
  714 + }
691 715 }
692 716 }
693   -
694   - } else {
695   - return '';
  717 + this.cellContentCache[index] = res;
696 718 }
  719 + return res;
697 720 }
698 721
699 722 public onRowClick($event: Event, alarm: AlarmDataInfo) {
... ... @@ -936,6 +959,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
936 959 isSorting(column: EntityColumn): boolean {
937 960 return column.type === DataKeyType.alarm && column.name.startsWith('details.');
938 961 }
  962 +
  963 + private clearCache() {
  964 + this.cellContentCache.length = 0;
  965 + this.cellStyleCache.length = 0;
  966 + this.rowStyleCache.length = 0;
  967 + }
939 968 }
940 969
941 970 class AlarmsDatasource implements DataSource<AlarmDataInfo> {
... ...
... ... @@ -42,9 +42,9 @@
42 42 matSort [matSortActive]="sortOrderProperty" [matSortDirection]="pageLinkSortDirection()" matSortDisableClear>
43 43 <ng-container [matColumnDef]="column.def" *ngFor="let column of columns; trackBy: trackByColumnDef;">
44 44 <mat-header-cell [ngStyle]="headerStyle(column)" *matHeaderCellDef mat-sort-header> {{ column.title }} </mat-header-cell>
45   - <mat-cell *matCellDef="let entity;"
46   - [innerHTML]="cellContent(entity, column)"
47   - [ngStyle]="cellStyle(entity, column)">
  45 + <mat-cell *matCellDef="let entity; let row = index"
  46 + [innerHTML]="cellContent(entity, column, row)"
  47 + [ngStyle]="cellStyle(entity, column, row)">
48 48 </mat-cell>
49 49 </ng-container>
50 50 <ng-container matColumnDef="actions" [stickyEnd]="enableStickyAction">
... ... @@ -84,8 +84,8 @@
84 84 <mat-header-row *matHeaderRowDef="displayedColumns; sticky: enableStickyHeader"></mat-header-row>
85 85 <mat-row [ngClass]="{'tb-current-entity': entityDatasource.isCurrentEntity(entity),
86 86 'invisible': entityDatasource.dataLoading}"
87   - *matRowDef="let entity; columns: displayedColumns;"
88   - [ngStyle]="rowStyle(entity)"
  87 + *matRowDef="let entity; columns: displayedColumns; let row = index"
  88 + [ngStyle]="rowStyle(entity, row)"
89 89 (click)="onRowClick($event, entity)" (dblclick)="onRowClick($event, entity, true)"></mat-row>
90 90 </table>
91 91 <span [fxShow]="(entityDatasource.isEmpty() | async) && !entityDatasource.dataLoading"
... ...
... ... @@ -40,7 +40,16 @@ import {
40 40 import { IWidgetSubscription } from '@core/api/widget-api.models';
41 41 import { UtilsService } from '@core/services/utils.service';
42 42 import { TranslateService } from '@ngx-translate/core';
43   -import { createLabelFromDatasource, deepClone, hashCode, isDefined, isNumber, isObject } from '@core/utils';
  43 +import {
  44 + createLabelFromDatasource,
  45 + deepClone,
  46 + hashCode,
  47 + isDefined,
  48 + isNumber,
  49 + isObject,
  50 + isString,
  51 + isUndefined
  52 +} from '@core/utils';
44 53 import cssjs from '@core/css/css';
45 54 import { CollectionViewer, DataSource } from '@angular/cdk/collections';
46 55 import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
... ... @@ -131,6 +140,10 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
131 140 public actionCellDescriptors: WidgetActionDescriptor[];
132 141 public entityDatasource: EntityDatasource;
133 142
  143 + private cellContentCache: Array<any> = [];
  144 + private cellStyleCache: Array<any> = [];
  145 + private rowStyleCache: Array<any> = [];
  146 +
134 147 private settings: EntitiesTableWidgetSettings;
135 148 private widgetConfig: WidgetConfig;
136 149 private subscription: IWidgetSubscription;
... ... @@ -222,6 +235,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
222 235 public onDataUpdated() {
223 236 this.updateTitle(true);
224 237 this.entityDatasource.dataUpdated();
  238 + this.clearCache();
225 239 }
226 240
227 241 public pageLinkSortDirection(): SortDirection {
... ... @@ -460,6 +474,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
460 474 if (this.actionCellDescriptors.length) {
461 475 this.displayedColumns.push('actions');
462 476 }
  477 + this.clearCache();
463 478 }
464 479 } as DisplayColumnsPanelData
465 480 },
... ... @@ -535,91 +550,98 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
535 550 return widthStyle(columnWidth);
536 551 }
537 552
538   - public rowStyle(entity: EntityData): any {
539   - let style: any = {};
540   - if (entity) {
541   - if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) {
  553 + public rowStyle(entity: EntityData, row: number): any {
  554 + let res = this.rowStyleCache[row];
  555 + if (!res) {
  556 + res = {};
  557 + if (entity && this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) {
542 558 try {
543   - style = this.rowStylesInfo.rowStyleFunction(entity, this.ctx);
544   - if (!isObject(style)) {
545   - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
  559 + res = this.rowStylesInfo.rowStyleFunction(entity, this.ctx);
  560 + if (!isObject(res)) {
  561 + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`);
546 562 }
547   - if (Array.isArray(style)) {
  563 + if (Array.isArray(res)) {
548 564 throw new TypeError(`Array instead of style object`);
549 565 }
550 566 } catch (e) {
551   - style = {};
  567 + res = {};
552 568 console.warn(`Row style function in widget '${this.ctx.widgetTitle}' ` +
553 569 `returns '${e}'. Please check your row style function.`);
554 570 }
555   - } else {
556   - style = {};
557 571 }
  572 + this.rowStyleCache[row] = res;
558 573 }
559   - return style;
560   - }
561   -
562   - public cellStyle(entity: EntityData, key: EntityColumn): any {
563   - let style: any = {};
564   - if (entity && key) {
565   - const styleInfo = this.stylesInfo[key.def];
566   - const value = getEntityValue(entity, key);
567   - if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
568   - try {
569   - style = styleInfo.cellStyleFunction(value, entity, this.ctx);
570   - if (!isObject(style)) {
571   - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
572   - }
573   - if (Array.isArray(style)) {
574   - throw new TypeError(`Array instead of style object`);
  574 + return res;
  575 + }
  576 +
  577 + public cellStyle(entity: EntityData, key: EntityColumn, row: number): any {
  578 + const col = this.columns.indexOf(key);
  579 + const index = row * this.columns.length + col;
  580 + let res = this.cellStyleCache[index];
  581 + if (!res) {
  582 + res = {};
  583 + if (entity && key) {
  584 + const styleInfo = this.stylesInfo[key.def];
  585 + const value = getEntityValue(entity, key);
  586 + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
  587 + try {
  588 + res = styleInfo.cellStyleFunction(value, entity, this.ctx);
  589 + if (!isObject(res)) {
  590 + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`);
  591 + }
  592 + if (Array.isArray(res)) {
  593 + throw new TypeError(`Array instead of style object`);
  594 + }
  595 + } catch (e) {
  596 + res = {};
  597 + console.warn(`Cell style function for data key '${key.label}' in widget '${this.ctx.widgetTitle}' ` +
  598 + `returns '${e}'. Please check your cell style function.`);
575 599 }
576   - } catch (e) {
577   - style = {};
578   - console.warn(`Cell style function for data key '${key.label}' in widget '${this.ctx.widgetTitle}' ` +
579   - `returns '${e}'. Please check your cell style function.`);
580 600 }
581   - } else {
582   - style = {};
  601 + this.cellStyleCache[index] = res;
583 602 }
584 603 }
585   - if (!style.width) {
  604 + if (!res.width) {
586 605 const columnWidth = this.columnWidth[key.def];
587   - style = {...style, ...widthStyle(columnWidth)};
  606 + res = Object.assign(res, widthStyle(columnWidth));
588 607 }
589   - return style;
590   - }
591   -
592   - public cellContent(entity: EntityData, key: EntityColumn): SafeHtml {
593   - if (entity && key) {
594   - const contentInfo = this.contentsInfo[key.def];
595   - const value = getEntityValue(entity, key);
596   - let content: string;
597   - if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
598   - try {
599   - content = contentInfo.cellContentFunction(value, entity, this.ctx);
600   - } catch (e) {
601   - content = '' + value;
  608 + return res;
  609 + }
  610 +
  611 + public cellContent(entity: EntityData, key: EntityColumn, row: number): SafeHtml {
  612 + const col = this.columns.indexOf(key);
  613 + const index = row * this.columns.length + col;
  614 + let res = this.cellContentCache[index];
  615 + if (isUndefined(res)) {
  616 + res = '';
  617 + if (entity && key) {
  618 + const contentInfo = this.contentsInfo[key.def];
  619 + const value = getEntityValue(entity, key);
  620 + let content: string;
  621 + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
  622 + try {
  623 + content = contentInfo.cellContentFunction(value, entity, this.ctx);
  624 + } catch (e) {
  625 + content = '' + value;
  626 + }
  627 + } else {
  628 + content = this.defaultContent(key, contentInfo, value);
602 629 }
603   - } else {
604   - content = this.defaultContent(key, contentInfo, value);
605   - }
606   -
607   - if (!isDefined(content)) {
608   - return '';
609 630
610   - } else {
611   - content = this.utils.customTranslation(content, content);
612   - switch (typeof content) {
613   - case 'string':
614   - return this.domSanitizer.bypassSecurityTrustHtml(content);
615   - default:
616   - return content;
  631 + if (isDefined(content)) {
  632 + content = this.utils.customTranslation(content, content);
  633 + switch (typeof content) {
  634 + case 'string':
  635 + res = this.domSanitizer.bypassSecurityTrustHtml(content);
  636 + break;
  637 + default:
  638 + res = content;
  639 + }
617 640 }
618 641 }
619   -
620   - } else {
621   - return '';
  642 + this.cellContentCache[index] = res;
622 643 }
  644 + return res;
623 645 }
624 646
625 647 private defaultContent(key: EntityColumn, contentInfo: CellContentInfo, value: any): any {
... ... @@ -672,6 +694,12 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
672 694 }
673 695 this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {entity}, entityLabel);
674 696 }
  697 +
  698 + private clearCache() {
  699 + this.cellContentCache.length = 0;
  700 + this.cellStyleCache.length = 0;
  701 + this.rowStyleCache.length = 0;
  702 + }
675 703 }
676 704
677 705 class EntityDatasource implements DataSource<EntityData> {
... ...
... ... @@ -39,23 +39,23 @@
39 39 </mat-toolbar>
40 40 <mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex
41 41 [(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()">
42   - <mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex; let index = index;" label="{{getTabLabel(source)}}">
  42 + <mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex; let index = index;" [label]="getTabLabel(source)">
43 43 <ng-template [ngIf]="isActiveTab(index)">
44 44 <div fxFlex class="table-container">
45 45 <table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp"
46 46 matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear>
47 47 <ng-container *ngIf="showTimestamp" [matColumnDef]="'0'">
48 48 <mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell>
49   - <mat-cell *matCellDef="let row;"
50   - [innerHTML]="cellContent(source, 0, row, row[0])"
51   - [ngStyle]="cellStyle(source, 0, row, row[0])">
  49 + <mat-cell *matCellDef="let row; let rowIndex = index"
  50 + [innerHTML]="cellContent(source, 0, row, row[0], rowIndex)"
  51 + [ngStyle]="cellStyle(source, 0, row, row[0], rowIndex)">
52 52 </mat-cell>
53 53 </ng-container>
54 54 <ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;">
55 55 <mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell>
56   - <mat-cell *matCellDef="let row;"
57   - [innerHTML]="cellContent(source, h.index, row, row[h.index])"
58   - [ngStyle]="cellStyle(source, h.index, row, row[h.index])">
  56 + <mat-cell *matCellDef="let row; let rowIndex = index"
  57 + [innerHTML]="cellContent(source, h.index, row, row[h.index], rowIndex)"
  58 + [ngStyle]="cellStyle(source, h.index, row, row[h.index], rowIndex)">
59 59 </mat-cell>
60 60 </ng-container>
61 61 <ng-container matColumnDef="actions" [stickyEnd]="enableStickyAction">
... ... @@ -93,8 +93,8 @@
93 93 </mat-cell>
94 94 </ng-container>
95 95 <mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: enableStickyHeader"></mat-header-row>
96   - <mat-row *matRowDef="let row; columns: source.displayedColumns;"
97   - [ngStyle]="rowStyle(source, row)"
  96 + <mat-row *matRowDef="let row; columns: source.displayedColumns; let rowIndex = index"
  97 + [ngStyle]="rowStyle(source, row, rowIndex)"
98 98 (click)="onRowClick($event, row)"></mat-row>
99 99 </table>
100 100 <span [fxShow]="source.timeseriesDatasource.isEmpty() | async"
... ...
... ... @@ -40,7 +40,7 @@ import {
40 40 } from '@shared/models/widget.models';
41 41 import { UtilsService } from '@core/services/utils.service';
42 42 import { TranslateService } from '@ngx-translate/core';
43   -import { hashCode, isDefined, isNumber, isObject } from '@core/utils';
  43 +import { hashCode, isDefined, isNumber, isObject, isUndefined } from '@core/utils';
44 44 import cssjs from '@core/css/css';
45 45 import { PageLink } from '@shared/models/page/page-link';
46 46 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
... ... @@ -120,6 +120,10 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
120 120 public sources: TimeseriesTableSource[];
121 121 public sourceIndex: number;
122 122
  123 + private cellContentCache: Array<any> = [];
  124 + private cellStyleCache: Array<any> = [];
  125 + private rowStyleCache: Array<any> = [];
  126 +
123 127 private settings: TimeseriesTableWidgetSettings;
124 128 private widgetConfig: WidgetConfig;
125 129 private data: Array<DatasourceData>;
... ... @@ -194,6 +198,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
194 198
195 199 public onDataUpdated() {
196 200 this.updateCurrentSourceData();
  201 + this.clearCache();
197 202 }
198 203
199 204 private initialize() {
... ... @@ -337,6 +342,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
337 342 onSourceIndexChanged() {
338 343 this.updateCurrentSourceData();
339 344 this.updateActiveEntityInfo();
  345 + this.clearCache();
340 346 }
341 347
342 348 private enterFilterMode() {
... ... @@ -378,6 +384,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
378 384 source.pageLink.sortOrder.property = sort.active;
379 385 source.pageLink.sortOrder.direction = Direction[sort.direction.toUpperCase()];
380 386 source.timeseriesDatasource.loadRows();
  387 + this.clearCache();
381 388 this.ctx.detectChanges();
382 389 }
383 390
... ... @@ -397,94 +404,109 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
397 404 return source.datasource.entityId;
398 405 }
399 406
400   - public rowStyle(source: TimeseriesTableSource, row: TimeseriesRow): any {
401   - let style: any = {};
402   - if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) {
403   - try {
404   - const rowData = source.rowDataTemplate;
405   - rowData.Timestamp = row[0];
406   - source.header.forEach((headerInfo) => {
407   - rowData[headerInfo.dataKey.name] = row[headerInfo.index];
408   - });
409   - style = this.rowStylesInfo.rowStyleFunction(rowData, this.ctx);
410   - if (!isObject(style)) {
411   - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
412   - }
413   - if (Array.isArray(style)) {
414   - throw new TypeError(`Array instead of style object`);
415   - }
416   - } catch (e) {
417   - style = {};
418   - console.warn(`Row style function in widget ` +
419   - `'${this.ctx.widgetConfig.title}' returns '${e}'. Please check your row style function.`);
420   - }
421   - }
422   - return style;
423   - }
424   -
425   - public cellStyle(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any): any {
426   - let style: any = {};
427   - if (index > 0) {
428   - const styleInfo = source.stylesInfo[index - 1];
429   - if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
  407 + public rowStyle(source: TimeseriesTableSource, row: TimeseriesRow, index: number): any {
  408 + let res = this.rowStyleCache[index];
  409 + if (!res) {
  410 + res = {};
  411 + if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) {
430 412 try {
431 413 const rowData = source.rowDataTemplate;
432 414 rowData.Timestamp = row[0];
433 415 source.header.forEach((headerInfo) => {
434 416 rowData[headerInfo.dataKey.name] = row[headerInfo.index];
435 417 });
436   - style = styleInfo.cellStyleFunction(value, rowData, this.ctx);
437   - if (!isObject(style)) {
438   - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
  418 + res = this.rowStylesInfo.rowStyleFunction(rowData, this.ctx);
  419 + if (!isObject(res)) {
  420 + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`);
439 421 }
440   - if (Array.isArray(style)) {
  422 + if (Array.isArray(res)) {
441 423 throw new TypeError(`Array instead of style object`);
442 424 }
443 425 } catch (e) {
444   - style = {};
445   - console.warn(`Cell style function for data key '${source.header[index - 1].dataKey.label}' in widget ` +
446   - `'${this.ctx.widgetConfig.title}' returns '${e}'. Please check your cell style function.`);
  426 + res = {};
  427 + console.warn(`Row style function in widget ` +
  428 + `'${this.ctx.widgetConfig.title}' returns '${e}'. Please check your row style function.`);
447 429 }
448 430 }
  431 + this.rowStyleCache[index] = res;
449 432 }
450   - return style;
451   - }
452   -
453   - public cellContent(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any): SafeHtml {
454   - if (index === 0) {
455   - return row.formattedTs;
456   - } else {
457   - let content;
458   - const contentInfo = source.contentsInfo[index - 1];
459   - if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
460   - try {
461   - const rowData = source.rowDataTemplate;
462   - rowData.Timestamp = row[0];
463   - source.header.forEach((headerInfo) => {
464   - rowData[headerInfo.dataKey.name] = row[headerInfo.index];
465   - });
466   - content = contentInfo.cellContentFunction(value, rowData, this.ctx);
467   - } catch (e) {
468   - content = '' + value;
  433 + return res;
  434 + }
  435 +
  436 + public cellStyle(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any, rowIndex: number): any {
  437 + const cacheIndex = rowIndex * (source.header.length + 1) + index;
  438 + let res = this.cellStyleCache[cacheIndex];
  439 + if (!res) {
  440 + res = {};
  441 + if (index > 0) {
  442 + const styleInfo = source.stylesInfo[index - 1];
  443 + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
  444 + try {
  445 + const rowData = source.rowDataTemplate;
  446 + rowData.Timestamp = row[0];
  447 + source.header.forEach((headerInfo) => {
  448 + rowData[headerInfo.dataKey.name] = row[headerInfo.index];
  449 + });
  450 + res = styleInfo.cellStyleFunction(value, rowData, this.ctx);
  451 + if (!isObject(res)) {
  452 + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`);
  453 + }
  454 + if (Array.isArray(res)) {
  455 + throw new TypeError(`Array instead of style object`);
  456 + }
  457 + } catch (e) {
  458 + res = {};
  459 + console.warn(`Cell style function for data key '${source.header[index - 1].dataKey.label}' in widget ` +
  460 + `'${this.ctx.widgetConfig.title}' returns '${e}'. Please check your cell style function.`);
  461 + }
469 462 }
470   - } else {
471   - const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals;
472   - const units = contentInfo.units || this.ctx.widgetConfig.units;
473   - content = this.ctx.utils.formatValue(value, decimals, units, true);
474 463 }
  464 + this.cellStyleCache[cacheIndex] = res;
  465 + }
  466 + return res;
  467 + }
475 468
476   - if (!isDefined(content)) {
477   - return '';
  469 + public cellContent(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any, rowIndex: number): SafeHtml {
  470 + const cacheIndex = rowIndex * (source.header.length + 1) + index ;
  471 + let res = this.cellContentCache[cacheIndex];
  472 + if (isUndefined(res)) {
  473 + res = '';
  474 + if (index === 0) {
  475 + res = row.formattedTs;
478 476 } else {
479   - content = this.utils.customTranslation(content, content);
480   - switch (typeof content) {
481   - case 'string':
482   - return this.domSanitizer.bypassSecurityTrustHtml(content);
483   - default:
484   - return content;
  477 + let content;
  478 + const contentInfo = source.contentsInfo[index - 1];
  479 + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
  480 + try {
  481 + const rowData = source.rowDataTemplate;
  482 + rowData.Timestamp = row[0];
  483 + source.header.forEach((headerInfo) => {
  484 + rowData[headerInfo.dataKey.name] = row[headerInfo.index];
  485 + });
  486 + content = contentInfo.cellContentFunction(value, rowData, this.ctx);
  487 + } catch (e) {
  488 + content = '' + value;
  489 + }
  490 + } else {
  491 + const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals;
  492 + const units = contentInfo.units || this.ctx.widgetConfig.units;
  493 + content = this.ctx.utils.formatValue(value, decimals, units, true);
  494 + }
  495 +
  496 + if (isDefined(content)) {
  497 + content = this.utils.customTranslation(content, content);
  498 + switch (typeof content) {
  499 + case 'string':
  500 + res = this.domSanitizer.bypassSecurityTrustHtml(content);
  501 + break;
  502 + default:
  503 + res = content;
  504 + }
485 505 }
486 506 }
  507 + this.cellContentCache[cacheIndex] = res;
487 508 }
  509 + return res;
488 510 }
489 511
490 512 public onRowClick($event: Event, row: TimeseriesRow) {
... ... @@ -530,6 +552,13 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
530 552
531 553 private loadCurrentSourceRow() {
532 554 this.sources[this.sourceIndex].timeseriesDatasource.loadRows();
  555 + this.clearCache();
  556 + }
  557 +
  558 + private clearCache() {
  559 + this.cellContentCache.length = 0;
  560 + this.cellStyleCache.length = 0;
  561 + this.rowStyleCache.length = 0;
533 562 }
534 563 }
535 564
... ...