|
|
|
@ -50,7 +50,7 @@ import { BehaviorSubject, fromEvent, merge, Observable, of, Subject } from 'rxjs |
|
|
|
import { emptyPageData, PageData } from '@shared/models/page/page-data'; |
|
|
|
import { EntityId } from '@shared/models/id/entity-id'; |
|
|
|
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; |
|
|
|
import { debounceTime, distinctUntilChanged, map, takeUntil, tap } from 'rxjs/operators'; |
|
|
|
import { catchError, debounceTime, distinctUntilChanged, map, takeUntil, tap } from 'rxjs/operators'; |
|
|
|
import { MatPaginator } from '@angular/material/paginator'; |
|
|
|
import { MatSort, SortDirection } from '@angular/material/sort'; |
|
|
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; |
|
|
|
@ -66,7 +66,7 @@ import { |
|
|
|
findColumnByEntityKey, |
|
|
|
findEntityKeyByColumnDef, |
|
|
|
fromEntityColumnDef, |
|
|
|
getCellContentInfo, |
|
|
|
getCellContentFunctionInfo, |
|
|
|
getCellStyleInfo, |
|
|
|
getColumnDefaultVisibility, |
|
|
|
getColumnSelectionAvailability, |
|
|
|
@ -106,6 +106,7 @@ import { hidePageSizePixelValue } from '@shared/models/constants'; |
|
|
|
import { AggregationType } from '@shared/models/time/time.models'; |
|
|
|
import { FormBuilder } from '@angular/forms'; |
|
|
|
import { DEFAULT_OVERLAY_POSITIONS } from '@shared/models/overlay.models'; |
|
|
|
import { CompiledTbFunction } from '@shared/models/js-function.models'; |
|
|
|
|
|
|
|
interface EntitiesTableWidgetSettings extends TableWidgetSettings { |
|
|
|
entitiesTitle: string; |
|
|
|
@ -164,13 +165,13 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
|
|
|
private defaultSortOrder = 'entityName'; |
|
|
|
|
|
|
|
private contentsInfo: {[key: string]: CellContentInfo} = {}; |
|
|
|
private stylesInfo: {[key: string]: CellStyleInfo} = {}; |
|
|
|
private stylesInfo: {[key: string]: Observable<CellStyleInfo>} = {}; |
|
|
|
private columnWidth: {[key: string]: string} = {}; |
|
|
|
private columnDefaultVisibility: {[key: string]: boolean} = {}; |
|
|
|
private columnSelectionAvailability: {[key: string]: boolean} = {}; |
|
|
|
private columnsWithCellClick: Array<number> = []; |
|
|
|
|
|
|
|
private rowStylesInfo: RowStyleInfo; |
|
|
|
private rowStylesInfo: Observable<RowStyleInfo>; |
|
|
|
|
|
|
|
private searchAction: WidgetAction = { |
|
|
|
name: 'action.search', |
|
|
|
@ -307,7 +308,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
|
|
|
this.showCellActionsMenu = isDefined(this.settings.showCellActionsMenu) ? this.settings.showCellActionsMenu : true; |
|
|
|
this.columnDisplayAction.show = isDefined(this.settings.enableSelectColumnDisplay) ? this.settings.enableSelectColumnDisplay : true; |
|
|
|
|
|
|
|
this.rowStylesInfo = getRowStyleInfo(this.settings, 'entity, ctx'); |
|
|
|
this.rowStylesInfo = getRowStyleInfo(this.ctx, this.settings, 'entity, ctx'); |
|
|
|
|
|
|
|
const pageSize = this.settings.defaultPageSize; |
|
|
|
if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) { |
|
|
|
@ -361,11 +362,13 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
|
|
|
} as EntityColumn |
|
|
|
); |
|
|
|
this.contentsInfo.entityName = { |
|
|
|
useCellContentFunction: false |
|
|
|
contentFunction: of({ |
|
|
|
useCellContentFunction: false |
|
|
|
}) |
|
|
|
}; |
|
|
|
this.stylesInfo.entityName = { |
|
|
|
this.stylesInfo.entityName = of({ |
|
|
|
useCellStyleFunction: false |
|
|
|
}; |
|
|
|
}); |
|
|
|
this.columnWidth.entityName = '0px'; |
|
|
|
this.columnDefaultVisibility.entityName = true; |
|
|
|
this.columnSelectionAvailability.entityName = true; |
|
|
|
@ -385,11 +388,13 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
|
|
|
} as EntityColumn |
|
|
|
); |
|
|
|
this.contentsInfo.entityLabel = { |
|
|
|
useCellContentFunction: false |
|
|
|
contentFunction: of({ |
|
|
|
useCellContentFunction: false |
|
|
|
}) |
|
|
|
}; |
|
|
|
this.stylesInfo.entityLabel = { |
|
|
|
this.stylesInfo.entityLabel = of({ |
|
|
|
useCellStyleFunction: false |
|
|
|
}; |
|
|
|
}); |
|
|
|
this.columnWidth.entityLabel = '0px'; |
|
|
|
this.columnDefaultVisibility.entityLabel = true; |
|
|
|
this.columnSelectionAvailability.entityLabel = true; |
|
|
|
@ -409,13 +414,18 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
|
|
|
} as EntityColumn |
|
|
|
); |
|
|
|
this.contentsInfo.entityType = { |
|
|
|
useCellContentFunction: true, |
|
|
|
cellContentFunction: (entityType: EntityType) => |
|
|
|
entityType ? this.translate.instant(entityTypeTranslations.get(entityType).type) : '' |
|
|
|
contentFunction: of({ |
|
|
|
useCellContentFunction: true, |
|
|
|
cellContentFunction: new CompiledTbFunction( |
|
|
|
(entityType: EntityType) => |
|
|
|
entityType ? this.translate.instant(entityTypeTranslations.get(entityType).type) : '', |
|
|
|
[] |
|
|
|
) |
|
|
|
}) |
|
|
|
}; |
|
|
|
this.stylesInfo.entityType = { |
|
|
|
this.stylesInfo.entityType = of({ |
|
|
|
useCellStyleFunction: false |
|
|
|
}; |
|
|
|
}); |
|
|
|
this.columnWidth.entityType = '0px'; |
|
|
|
this.columnDefaultVisibility.entityType = true; |
|
|
|
this.columnSelectionAvailability.entityType = true; |
|
|
|
@ -447,8 +457,14 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings, 'value, entity, ctx'); |
|
|
|
this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, entity, ctx'); |
|
|
|
this.stylesInfo[dataKey.def] = getCellStyleInfo(this.ctx, keySettings, 'value, entity, ctx'); |
|
|
|
const contentFunctionInfo = getCellContentFunctionInfo(this.ctx, keySettings, 'value, entity, ctx'); |
|
|
|
const contentInfo: CellContentInfo = { |
|
|
|
contentFunction: contentFunctionInfo, |
|
|
|
units: dataKey.units, |
|
|
|
decimals: dataKey.decimals |
|
|
|
}; |
|
|
|
this.contentsInfo[dataKey.def] = contentInfo; |
|
|
|
this.contentsInfo[dataKey.def].units = dataKey.units; |
|
|
|
this.contentsInfo[dataKey.def].decimals = dataKey.decimals; |
|
|
|
this.columnWidth[dataKey.def] = getColumnWidth(keySettings); |
|
|
|
@ -595,98 +611,135 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
|
|
|
return widthStyle(columnWidth); |
|
|
|
} |
|
|
|
|
|
|
|
public rowStyle(entity: EntityData, row: number): any { |
|
|
|
public rowStyle(entity: EntityData, row: number): Observable<any> { |
|
|
|
let style$: Observable<any>; |
|
|
|
let res = this.rowStyleCache[row]; |
|
|
|
if (!res) { |
|
|
|
res = {}; |
|
|
|
if (entity && this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) { |
|
|
|
try { |
|
|
|
res = this.rowStylesInfo.rowStyleFunction(entity, this.ctx); |
|
|
|
if (!isObject(res)) { |
|
|
|
throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`); |
|
|
|
} |
|
|
|
if (Array.isArray(res)) { |
|
|
|
throw new TypeError(`Array instead of style object`); |
|
|
|
style$ = this.rowStylesInfo.pipe( |
|
|
|
map(styleInfo => { |
|
|
|
if (styleInfo.useRowStyleFunction && styleInfo.rowStyleFunction) { |
|
|
|
const style = styleInfo.rowStyleFunction.execute(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`); |
|
|
|
} |
|
|
|
return style; |
|
|
|
} else { |
|
|
|
return {}; |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
res = {}; |
|
|
|
}), |
|
|
|
catchError(e => { |
|
|
|
console.warn(`Row style function in widget '${this.ctx.widgetTitle}' ` + |
|
|
|
`returns '${e}'. Please check your row style function.`); |
|
|
|
} |
|
|
|
} |
|
|
|
this.rowStyleCache[row] = res; |
|
|
|
return of({}); |
|
|
|
}) |
|
|
|
); |
|
|
|
style$ = style$.pipe( |
|
|
|
tap((style) => { |
|
|
|
this.rowStyleCache[row] = style; |
|
|
|
}) |
|
|
|
); |
|
|
|
} else { |
|
|
|
style$ = of(res); |
|
|
|
} |
|
|
|
return res; |
|
|
|
return style$; |
|
|
|
} |
|
|
|
|
|
|
|
public cellStyle(entity: EntityData, key: EntityColumn, row: number): any { |
|
|
|
public cellStyle(entity: EntityData, key: EntityColumn, row: number): Observable<any> { |
|
|
|
let style$: Observable<any>; |
|
|
|
const col = this.columns.indexOf(key); |
|
|
|
const index = row * this.columns.length + col; |
|
|
|
let res = this.cellStyleCache[index]; |
|
|
|
if (!res) { |
|
|
|
res = {}; |
|
|
|
if (entity && key) { |
|
|
|
const styleInfo = this.stylesInfo[key.def]; |
|
|
|
const value = getEntityValue(entity, key); |
|
|
|
if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { |
|
|
|
try { |
|
|
|
res = styleInfo.cellStyleFunction(value, entity, this.ctx); |
|
|
|
if (!isObject(res)) { |
|
|
|
throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`); |
|
|
|
} |
|
|
|
if (Array.isArray(res)) { |
|
|
|
throw new TypeError(`Array instead of style object`); |
|
|
|
style$ = this.stylesInfo[key.def].pipe( |
|
|
|
map((styleInfo) => { |
|
|
|
const value = getEntityValue(entity, key); |
|
|
|
if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { |
|
|
|
const style = styleInfo.cellStyleFunction.execute(value, 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`); |
|
|
|
} |
|
|
|
return style; |
|
|
|
} else { |
|
|
|
return {}; |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
res = {}; |
|
|
|
}), |
|
|
|
catchError(e => { |
|
|
|
console.warn(`Cell style function for data key '${key.label}' in widget '${this.ctx.widgetTitle}' ` + |
|
|
|
`returns '${e}'. Please check your cell style function.`); |
|
|
|
} |
|
|
|
} |
|
|
|
this.cellStyleCache[index] = res; |
|
|
|
return of({}); |
|
|
|
}) |
|
|
|
); |
|
|
|
} else { |
|
|
|
style$ = of({}); |
|
|
|
} |
|
|
|
style$ = style$.pipe( |
|
|
|
map((style) => { |
|
|
|
if (!style.width) { |
|
|
|
const columnWidth = this.columnWidth[key.def]; |
|
|
|
style = Object.assign(style, widthStyle(columnWidth)); |
|
|
|
} |
|
|
|
return style; |
|
|
|
}), |
|
|
|
tap((style) => { |
|
|
|
this.cellStyleCache[index] = style; |
|
|
|
}) |
|
|
|
); |
|
|
|
} else { |
|
|
|
style$ = of(res); |
|
|
|
} |
|
|
|
if (!res.width) { |
|
|
|
const columnWidth = this.columnWidth[key.def]; |
|
|
|
res = Object.assign(res, widthStyle(columnWidth)); |
|
|
|
} |
|
|
|
return res; |
|
|
|
return style$; |
|
|
|
} |
|
|
|
|
|
|
|
public cellContent(entity: EntityData, key: EntityColumn, row: number): SafeHtml { |
|
|
|
public cellContent(entity: EntityData, key: EntityColumn, row: number): Observable<SafeHtml> { |
|
|
|
let content$: Observable<SafeHtml>; |
|
|
|
const col = this.columns.indexOf(key); |
|
|
|
const index = row * this.columns.length + col; |
|
|
|
let res = this.cellContentCache[index]; |
|
|
|
if (isUndefined(res)) { |
|
|
|
res = ''; |
|
|
|
if (entity && key) { |
|
|
|
const contentInfo = this.contentsInfo[key.def]; |
|
|
|
const value = getEntityValue(entity, key); |
|
|
|
let content: string; |
|
|
|
if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { |
|
|
|
try { |
|
|
|
content = contentInfo.cellContentFunction(value, entity, this.ctx); |
|
|
|
} catch (e) { |
|
|
|
content = '' + value; |
|
|
|
} |
|
|
|
} else { |
|
|
|
content = this.defaultContent(key, contentInfo, value); |
|
|
|
} |
|
|
|
|
|
|
|
if (isDefined(content)) { |
|
|
|
content = this.utils.customTranslation(content, content); |
|
|
|
switch (typeof content) { |
|
|
|
case 'string': |
|
|
|
res = this.domSanitizer.bypassSecurityTrustHtml(content); |
|
|
|
break; |
|
|
|
default: |
|
|
|
res = content; |
|
|
|
const contentInfo = this.contentsInfo[key.def]; |
|
|
|
content$ = contentInfo.contentFunction.pipe( |
|
|
|
map((contentFunction) => { |
|
|
|
let content: any = ''; |
|
|
|
if (entity && key) { |
|
|
|
const contentInfo = this.contentsInfo[key.def]; |
|
|
|
const value = getEntityValue(entity, key); |
|
|
|
if (contentFunction.useCellContentFunction && contentFunction.cellContentFunction) { |
|
|
|
try { |
|
|
|
content = contentFunction.cellContentFunction.execute(value, entity, this.ctx); |
|
|
|
} catch (e) { |
|
|
|
content = '' + value; |
|
|
|
} |
|
|
|
} else { |
|
|
|
content = this.defaultContent(key, contentInfo, value); |
|
|
|
} |
|
|
|
if (isDefined(content)) { |
|
|
|
content = this.utils.customTranslation(content, content); |
|
|
|
switch (typeof content) { |
|
|
|
case 'string': |
|
|
|
content = this.domSanitizer.bypassSecurityTrustHtml(content); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
this.cellContentCache[index] = res; |
|
|
|
return content; |
|
|
|
}) |
|
|
|
); |
|
|
|
content$ = content$.pipe( |
|
|
|
tap((content) => { |
|
|
|
this.cellContentCache[index] = content; |
|
|
|
}) |
|
|
|
); |
|
|
|
} else { |
|
|
|
content$ = of(res); |
|
|
|
} |
|
|
|
return res; |
|
|
|
return content$; |
|
|
|
} |
|
|
|
|
|
|
|
private defaultContent(key: EntityColumn, contentInfo: CellContentInfo, value: any): any { |
|
|
|
|