Commit a0623e8e86064c592d141d2b90e2003727989bd3
1 parent
c2c2f55d
UI: Add cache cellContent, cellStyle, rowStyle in entity tables widget
Showing
7 changed files
with
301 additions
and
215 deletions
... | ... | @@ -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 | ... | ... |