0) {
this.defaultPageSize = pageSize;
@@ -383,18 +396,21 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
keySettings.columnWidth = '120px';
}
}
- this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings);
+ this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings, 'value, alarm, ctx');
this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx');
this.contentsInfo[dataKey.def].units = dataKey.units;
this.contentsInfo[dataKey.def].decimals = dataKey.decimals;
this.columnWidth[dataKey.def] = getColumnWidth(keySettings);
+ this.columnDefaultVisibility[dataKey.def] = getColumnDefaultVisibility(keySettings);
+ this.columnSelectionAvailability[dataKey.def] = getColumnSelectionAvailability(keySettings);
this.columns.push(dataKey);
if (dataKey.type !== DataKeyType.alarm) {
latestDataKeys.push(dataKey);
}
});
- this.displayedColumns.push(...this.columns.map(column => column.def));
+ this.displayedColumns.push(...this.columns.filter(column => this.columnDefaultVisibility[column.def])
+ .map(column => column.def));
}
if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) {
this.defaultSortOrder = this.utils.customTranslation(this.settings.defaultSortOrder, this.settings.defaultSortOrder);
@@ -450,7 +466,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
return {
title: column.title,
def: column.def,
- display: this.displayedColumns.indexOf(column.def) > -1
+ display: this.displayedColumns.indexOf(column.def) > -1,
+ selectable: this.columnSelectionAvailability[column.def]
};
});
@@ -464,7 +481,9 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
if (this.enableSelection) {
this.displayedColumns.unshift('select');
}
- this.displayedColumns.push('actions');
+ if (this.actionCellDescriptors.length) {
+ this.displayedColumns.push('actions');
+ }
}
} as DisplayColumnsPanelData
},
@@ -590,6 +609,30 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
return widthStyle(columnWidth);
}
+ public rowStyle(alarm: AlarmDataInfo): any {
+ let style: any = {};
+ if (alarm) {
+ if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) {
+ try {
+ style = this.rowStylesInfo.rowStyleFunction(alarm, this.ctx);
+ if (!isObject(style)) {
+ throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
+ }
+ if (Array.isArray(style)) {
+ throw new TypeError(`Array instead of style object`);
+ }
+ } catch (e) {
+ style = {};
+ console.warn(`Row style function in widget '${this.ctx.widgetTitle}' ` +
+ `returns '${e}'. Please check your row style function.`);
+ }
+ } else {
+ style = {};
+ }
+ }
+ return style;
+ }
+
public cellStyle(alarm: AlarmDataInfo, key: EntityColumn): any {
let style: any = {};
if (alarm && key) {
@@ -597,7 +640,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
const value = getAlarmValue(alarm, key);
if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
try {
- style = styleInfo.cellStyleFunction(value);
+ style = styleInfo.cellStyleFunction(value, alarm, this.ctx);
if (!isObject(style)) {
throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
}
@@ -639,6 +682,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
return '';
} else {
+ content = this.utils.customTranslation(content, content);
switch (typeof content) {
case 'string':
return this.domSanitizer.bypassSecurityTrustHtml(content);
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html
index bb8ae1b513..3104899d04 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html
@@ -17,7 +17,7 @@
-->
-
{{ column.title }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html
index cc6a6a198b..680ed3ad97 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html
@@ -47,7 +47,7 @@
[ngStyle]="cellStyle(entity, column)">
-
+
@@ -81,10 +81,11 @@
-
+
0) {
this.defaultPageSize = pageSize;
@@ -297,6 +313,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
useCellStyleFunction: false
};
this.columnWidth.entityName = '0px';
+ this.columnDefaultVisibility.entityName = true;
+ this.columnSelectionAvailability.entityName = true;
}
if (displayEntityLabel) {
this.columns.push(
@@ -318,6 +336,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
useCellStyleFunction: false
};
this.columnWidth.entityLabel = '0px';
+ this.columnDefaultVisibility.entityLabel = true;
+ this.columnSelectionAvailability.entityLabel = true;
}
if (displayEntityType) {
this.columns.push(
@@ -339,6 +359,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
useCellStyleFunction: false
};
this.columnWidth.entityType = '0px';
+ this.columnDefaultVisibility.entityType = true;
+ this.columnSelectionAvailability.entityType = true;
}
const dataKeys: Array = [];
@@ -366,14 +388,17 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
}
}
- this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings);
+ this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings, 'value, entity, ctx');
this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, entity, ctx');
this.contentsInfo[dataKey.def].units = dataKey.units;
this.contentsInfo[dataKey.def].decimals = dataKey.decimals;
this.columnWidth[dataKey.def] = getColumnWidth(keySettings);
+ this.columnDefaultVisibility[dataKey.def] = getColumnDefaultVisibility(keySettings);
+ this.columnSelectionAvailability[dataKey.def] = getColumnSelectionAvailability(keySettings);
this.columns.push(dataKey);
});
- this.displayedColumns.push(...this.columns.map(column => column.def));
+ this.displayedColumns.push(...this.columns.filter(column => this.columnDefaultVisibility[column.def])
+ .map(column => column.def));
}
if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) {
@@ -420,7 +445,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
return {
title: column.title,
def: column.def,
- display: this.displayedColumns.indexOf(column.def) > -1
+ display: this.displayedColumns.indexOf(column.def) > -1,
+ selectable: this.columnSelectionAvailability[column.def]
};
});
@@ -431,7 +457,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
columns,
columnsUpdated: (newColumns) => {
this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def);
- this.displayedColumns.push('actions');
+ if (this.actionCellDescriptors.length) {
+ this.displayedColumns.push('actions');
+ }
}
} as DisplayColumnsPanelData
},
@@ -507,6 +535,30 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
return widthStyle(columnWidth);
}
+ public rowStyle(entity: EntityData): any {
+ let style: any = {};
+ if (entity) {
+ if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) {
+ try {
+ style = this.rowStylesInfo.rowStyleFunction(entity, this.ctx);
+ if (!isObject(style)) {
+ throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
+ }
+ if (Array.isArray(style)) {
+ throw new TypeError(`Array instead of style object`);
+ }
+ } catch (e) {
+ style = {};
+ console.warn(`Row style function in widget '${this.ctx.widgetTitle}' ` +
+ `returns '${e}'. Please check your row style function.`);
+ }
+ } else {
+ style = {};
+ }
+ }
+ return style;
+ }
+
public cellStyle(entity: EntityData, key: EntityColumn): any {
let style: any = {};
if (entity && key) {
@@ -514,7 +566,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
const value = getEntityValue(entity, key);
if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
try {
- style = styleInfo.cellStyleFunction(value);
+ style = styleInfo.cellStyleFunction(value, entity, this.ctx);
if (!isObject(style)) {
throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
}
@@ -556,6 +608,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
return '';
} else {
+ content = this.utils.customTranslation(content, content);
switch (typeof content) {
case 'string':
return this.domSanitizer.bypassSecurityTrustHtml(content);
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts
index b082bbe35d..a6bee06d07 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts
@@ -24,20 +24,28 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
const tinycolor = tinycolor_;
+type ColumnVisibilityOptions = 'visible' | 'hidden';
+
+type ColumnSelectionOptions = 'enabled' | 'disabled';
+
export interface TableWidgetSettings {
enableSearch: boolean;
- enableSelectColumnDisplay: boolean;
+ enableStickyAction: boolean;
+ enableStickyHeader: boolean;
displayPagination: boolean;
defaultPageSize: number;
- defaultSortOrder: string;
+ useRowStyleFunction: boolean;
+ rowStyleFunction?: string;
}
export interface TableWidgetDataKeySettings {
columnWidth?: string;
useCellStyleFunction: boolean;
- cellStyleFunction: string;
+ cellStyleFunction?: string;
useCellContentFunction: boolean;
- cellContentFunction: string;
+ cellContentFunction?: string;
+ defaultColumnVisibility: ColumnVisibilityOptions;
+ columnSelectionToDisplay: ColumnSelectionOptions;
}
export interface EntityData {
@@ -58,6 +66,7 @@ export interface DisplayColumn {
title: string;
def: string;
display: boolean;
+ selectable: boolean;
}
export type CellContentFunction = (...args: any[]) => string;
@@ -69,13 +78,20 @@ export interface CellContentInfo {
decimals?: number;
}
-export type CellStyleFunction = (value: any) => any;
+export type CellStyleFunction = (...args: any[]) => any;
export interface CellStyleInfo {
useCellStyleFunction: boolean;
cellStyleFunction?: CellStyleFunction;
}
+export type RowStyleFunction = (...args: any[]) => any;
+
+export interface RowStyleInfo {
+ useRowStyleFunction: boolean;
+ rowStyleFunction?: RowStyleFunction;
+}
+
export function entityDataSortOrderFromString(strSortOrder: string, columns: EntityColumn[]): EntityDataSortOrder {
if (!strSortOrder && !strSortOrder.length) {
@@ -196,14 +212,35 @@ export function getAlarmValue(alarm: AlarmDataInfo, key: EntityColumn) {
}
}
-export function getCellStyleInfo(keySettings: TableWidgetDataKeySettings): CellStyleInfo {
+export function getRowStyleInfo(settings: TableWidgetSettings, ...args: string[]): RowStyleInfo {
+ let rowStyleFunction: RowStyleFunction = null;
+ let useRowStyleFunction = false;
+
+ if (settings.useRowStyleFunction === true) {
+ if (isDefined(settings.rowStyleFunction) && settings.rowStyleFunction.length > 0) {
+ try {
+ rowStyleFunction = new Function(...args, settings.rowStyleFunction) as RowStyleFunction;
+ useRowStyleFunction = true;
+ } catch (e) {
+ rowStyleFunction = null;
+ useRowStyleFunction = false;
+ }
+ }
+ }
+ return {
+ useRowStyleFunction,
+ rowStyleFunction
+ };
+}
+
+export function getCellStyleInfo(keySettings: TableWidgetDataKeySettings, ...args: string[]): CellStyleInfo {
let cellStyleFunction: CellStyleFunction = null;
let useCellStyleFunction = false;
if (keySettings.useCellStyleFunction === true) {
if (isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) {
try {
- cellStyleFunction = new Function('value', keySettings.cellStyleFunction) as CellStyleFunction;
+ cellStyleFunction = new Function(...args, keySettings.cellStyleFunction) as CellStyleFunction;
useCellStyleFunction = true;
} catch (e) {
cellStyleFunction = null;
@@ -251,6 +288,13 @@ export function widthStyle(width: string): any {
return widthStyleObj;
}
+export function getColumnDefaultVisibility(keySettings: TableWidgetDataKeySettings): boolean {
+ return !(isDefined(keySettings.defaultColumnVisibility) && keySettings.defaultColumnVisibility === 'hidden');
+}
+
+export function getColumnSelectionAvailability(keySettings: TableWidgetDataKeySettings): boolean {
+ return !(isDefined(keySettings.columnSelectionToDisplay) && keySettings.columnSelectionToDisplay === 'disabled');
+}
export function constructTableCssString(widgetConfig: WidgetConfig): string {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html
index 0b41d35785..f1c37d4cdd 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html
@@ -48,17 +48,17 @@
Timestamp
+ [ngStyle]="cellStyle(source, 0, row, row[0])">
{{ h.dataKey.label }}
+ [ngStyle]="cellStyle(source, h.index, row, row[h.index])">
-
+
@@ -92,8 +92,9 @@
-
+
;
public displayPagination = true;
+ public enableStickyHeader = true;
+ public enableStickyAction = true;
public pageSizeOptions;
public textSearchMode = false;
public textSearch: string = null;
@@ -129,6 +131,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
public showTimestamp = true;
private dateFormatFilter: string;
+ private rowStylesInfo: RowStyleInfo;
+
private subscriptions: Subscription[] = [];
private searchAction: WidgetAction = {
@@ -196,11 +200,16 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
this.actionCellDescriptors = this.ctx.actionsApi.getActionDescriptors('actionCellButton');
+ this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true;
this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true;
+ this.enableStickyHeader = isDefined(this.settings.enableStickyHeader) ? this.settings.enableStickyHeader : true;
+ this.enableStickyAction = isDefined(this.settings.enableStickyAction) ? this.settings.enableStickyAction : true;
this.hideEmptyLines = isDefined(this.settings.hideEmptyLines) ? this.settings.hideEmptyLines : false;
this.showTimestamp = this.settings.showTimestamp !== false;
this.dateFormatFilter = (this.settings.showMilliseconds !== true) ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss.SSS';
+ this.rowStylesInfo = getRowStyleInfo(this.settings, 'rowData, ctx');
+
const pageSize = this.settings.defaultPageSize;
if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) {
this.defaultPageSize = pageSize;
@@ -258,13 +267,15 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
});
source.displayedColumns.push(index + '');
source.rowDataTemplate[dataKey.label] = null;
- source.stylesInfo.push(getCellStyleInfo(keySettings));
+ source.stylesInfo.push(getCellStyleInfo(keySettings, 'value, rowData, ctx'));
const cellContentInfo = getCellContentInfo(keySettings, 'value, rowData, ctx');
cellContentInfo.units = dataKey.units;
cellContentInfo.decimals = dataKey.decimals;
source.contentsInfo.push(cellContentInfo);
}
- source.displayedColumns.push('actions');
+ if (this.actionCellDescriptors.length) {
+ source.displayedColumns.push('actions');
+ }
const tsDatasource = new TimeseriesDatasource(source, this.hideEmptyLines, this.dateFormatFilter, this.datePipe);
tsDatasource.dataUpdated(this.data);
this.sources.push(source);
@@ -376,13 +387,43 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
return source.datasource.entityId;
}
- public cellStyle(source: TimeseriesTableSource, index: number, value: any): any {
+ public rowStyle(source: TimeseriesTableSource, row: TimeseriesRow): any {
+ let style: any = {};
+ if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) {
+ try {
+ const rowData = source.rowDataTemplate;
+ rowData.Timestamp = row[0];
+ source.header.forEach((headerInfo) => {
+ rowData[headerInfo.dataKey.name] = row[headerInfo.index];
+ });
+ style = this.rowStylesInfo.rowStyleFunction(rowData, this.ctx);
+ if (!isObject(style)) {
+ throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
+ }
+ if (Array.isArray(style)) {
+ throw new TypeError(`Array instead of style object`);
+ }
+ } catch (e) {
+ style = {};
+ console.warn(`Row style function in widget ` +
+ `'${this.ctx.widgetConfig.title}' returns '${e}'. Please check your row style function.`);
+ }
+ }
+ return style;
+ }
+
+ public cellStyle(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any): any {
let style: any = {};
if (index > 0) {
const styleInfo = source.stylesInfo[index - 1];
if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
try {
- style = styleInfo.cellStyleFunction(value);
+ const rowData = source.rowDataTemplate;
+ rowData.Timestamp = row[0];
+ source.header.forEach((headerInfo) => {
+ rowData[headerInfo.dataKey.name] = row[headerInfo.index];
+ });
+ style = styleInfo.cellStyleFunction(value, rowData, this.ctx);
if (!isObject(style)) {
throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`);
}
@@ -425,6 +466,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
if (!isDefined(content)) {
return '';
} else {
+ content = this.utils.customTranslation(content, content);
switch (typeof content) {
case 'string':
return this.domSanitizer.bypassSecurityTrustHtml(content);
diff --git a/ui-ngx/src/app/shared/pipe/public-api.ts b/ui-ngx/src/app/shared/pipe/public-api.ts
index cf8e61b5e9..07543f39fd 100644
--- a/ui-ngx/src/app/shared/pipe/public-api.ts
+++ b/ui-ngx/src/app/shared/pipe/public-api.ts
@@ -21,3 +21,4 @@ export * from './milliseconds-to-time-string.pipe';
export * from './nospace.pipe';
export * from './truncate.pipe';
export * from './file-size.pipe';
+export * from './selectable-columns.pipe';
diff --git a/ui-ngx/src/app/shared/pipe/selectable-columns.pipe.ts b/ui-ngx/src/app/shared/pipe/selectable-columns.pipe.ts
new file mode 100644
index 0000000000..acfa00693f
--- /dev/null
+++ b/ui-ngx/src/app/shared/pipe/selectable-columns.pipe.ts
@@ -0,0 +1,25 @@
+///
+/// Copyright © 2016-2021 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { Pipe, PipeTransform } from '@angular/core';
+import { DisplayColumn } from '@home/components/widget/lib/table-widget.models';
+
+@Pipe({ name: 'selectableColumns' })
+export class SelectableColumnsPipe implements PipeTransform {
+ transform(allColumns: DisplayColumn[]): DisplayColumn[] {
+ return allColumns.filter(column => column.selectable);
+ }
+}
diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts
index 19daca2301..6b9353b8ae 100644
--- a/ui-ngx/src/app/shared/shared.module.ts
+++ b/ui-ngx/src/app/shared/shared.module.ts
@@ -139,6 +139,7 @@ import { ContactComponent } from '@shared/components/contact.component';
import { TimezoneSelectComponent } from '@shared/components/time/timezone-select.component';
import { FileSizePipe } from '@shared/pipe/file-size.pipe';
import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-search.component';
+import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe';
@NgModule({
providers: [
@@ -223,6 +224,7 @@ import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-
TruncatePipe,
TbJsonPipe,
FileSizePipe,
+ SelectableColumnsPipe,
KeyboardShortcutPipe,
TbJsonToStringDirective,
JsonObjectEditDialogComponent,
@@ -393,6 +395,7 @@ import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-
TbJsonPipe,
KeyboardShortcutPipe,
FileSizePipe,
+ SelectableColumnsPipe,
TranslateModule,
JsonObjectEditDialogComponent,
HistorySelectorComponent,