9 } from '@angular/core';
11 import _ from 'lodash';
13 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
14 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
15 import { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe';
16 import { TableComponent } from '../table/table.component';
18 interface KeyValueItem {
24 * Display the given data in a 2 column data table. The left column
25 * shows the 'key' attribute, the right column the 'value' attribute.
26 * The data table has the following characteristics:
27 * - No header and footer is displayed
28 * - The relation of the width for the columns 'key' and 'value' is 1:3
29 * - The 'key' column is displayed in bold text
32 selector: 'cd-table-key-value',
33 templateUrl: './table-key-value.component.html',
34 styleUrls: ['./table-key-value.component.scss']
36 export class TableKeyValueComponent implements OnInit, OnChanges {
37 @ViewChild(TableComponent, { static: true })
38 table: TableComponent;
43 autoReload: any = 5000;
45 renderObjects = false;
46 // Only used if objects are rendered
48 appendParentKey = true;
52 hideKeys: string[] = []; // Keys of pairs not to be displayed
54 // If set, the classAddingTpl is used to enable different css for different values
56 customCss?: { [css: string]: number | string | ((any: any) => boolean) };
58 columns: Array<CdTableColumn> = [];
59 tableData: KeyValueItem[];
62 * The function that will be called to update the input data.
65 fetchData = new EventEmitter();
67 constructor(private datePipe: CdDatePipe) {}
74 cellTransformation: CellTemplate.bold
82 this.columns[1].cellTransformation = CellTemplate.classAdding;
84 // We need to subscribe the 'fetchData' event here and not in the
85 // HTML template, otherwise the data table will display the loading
86 // indicator infinitely if data is only bound via '[data]="xyz"'.
87 // See for 'loadingIndicator' in 'TableComponent::ngOnInit()'.
88 if (this.fetchData.observers.length > 0) {
89 this.table.fetchData.subscribe(() => {
90 // Forward event triggered by the 'cd-table' data table.
91 this.fetchData.emit();
103 return; // Wait for data
105 let pairs = this.makePairs(this.data);
107 pairs = pairs.filter((pair) => !this.hideKeys.includes(pair.key));
109 this.tableData = pairs;
112 private makePairs(data: any): KeyValueItem[] {
113 let result: KeyValueItem[] = [];
115 return undefined; // Wait for data
116 } else if (_.isArray(data)) {
117 result = this.makePairsFromArray(data);
118 } else if (_.isObject(data)) {
119 result = this.makePairsFromObject(data);
121 throw new Error('Wrong data format');
125 item.value = this.convertValue(item.value);
128 .filter((i) => i.value !== null);
129 return _.sortBy(this.renderObjects ? this.insertFlattenObjects(result) : result, 'key');
132 private makePairsFromArray(data: any[]): KeyValueItem[] {
133 let temp: any[] = [];
134 const first = data[0];
135 if (_.isArray(first)) {
136 if (first.length === 2) {
137 temp = data.map((a) => ({
143 `Array contains too many elements (${first.length}). ` +
144 `Needs to be of type [string, any][]`
147 } else if (_.isObject(first)) {
148 if (_.has(first, 'key') && _.has(first, 'value')) {
152 (previous: any[], item) => previous.concat(this.makePairsFromObject(item)),
160 private makePairsFromObject(data: any): KeyValueItem[] {
161 return Object.keys(data).map((k) => ({
167 private insertFlattenObjects(data: KeyValueItem[]): any[] {
168 return _.flattenDeep(
170 const value = item.value;
171 const isObject = _.isObject(value);
172 if (!isObject || _.isEmpty(value)) {
178 return this.splitItemIntoItems(item);
184 * Split item into items will call _makePairs inside _makePairs (recursion), in oder to split
185 * the object item up into items as planned.
187 private splitItemIntoItems(data: { key: string; value: object }): KeyValueItem[] {
188 return this.makePairs(data.value).map((item) => {
189 if (this.appendParentKey) {
190 item.key = data.key + ' ' + item.key;
196 private convertValue(value: any): KeyValueItem {
197 if (_.isArray(value)) {
198 if (_.isEmpty(value) && this.hideEmpty) {
201 value = value.map((item) => (_.isObject(item) ? JSON.stringify(item) : item)).join(', ');
202 } else if (_.isObject(value)) {
203 if ((this.hideEmpty && _.isEmpty(value)) || !this.renderObjects) {
206 } else if (_.isString(value)) {
207 if (value === '' && this.hideEmpty) {
210 if (this.isDate(value)) {
211 value = this.datePipe.transform(value) || value;
218 private isDate(s: string) {
219 const sep = '[ -:.TZ]';
220 const n = '\\d{2}' + sep;
221 // year - m - d - h : m : s . someRest Z (if UTC)
222 return s.match(new RegExp('^\\d{4}' + sep + n + n + n + n + n + '\\d*' + 'Z?$'));