]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | import { |
2 | Component, | |
3 | EventEmitter, | |
4 | Input, | |
5 | OnChanges, | |
6 | OnInit, | |
7 | Output, | |
8 | ViewChild | |
9 | } from '@angular/core'; | |
10 | ||
11 | import * as _ from 'lodash'; | |
12 | ||
13 | import { CellTemplate } from '../../enum/cell-template.enum'; | |
14 | import { CdTableColumn } from '../../models/cd-table-column'; | |
81eedcae | 15 | import { CdDatePipe } from '../../pipes/cd-date.pipe'; |
11fdf7f2 TL |
16 | import { TableComponent } from '../table/table.component'; |
17 | ||
92f5a8d4 | 18 | interface KeyValueItem { |
11fdf7f2 TL |
19 | key: string; |
20 | value: any; | |
21 | } | |
22 | ||
23 | /** | |
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 | |
30 | */ | |
31 | @Component({ | |
32 | selector: 'cd-table-key-value', | |
33 | templateUrl: './table-key-value.component.html', | |
34 | styleUrls: ['./table-key-value.component.scss'] | |
35 | }) | |
36 | export class TableKeyValueComponent implements OnInit, OnChanges { | |
37 | @ViewChild(TableComponent) | |
38 | table: TableComponent; | |
39 | ||
40 | @Input() | |
41 | data: any; | |
42 | @Input() | |
43 | autoReload: any = 5000; | |
44 | @Input() | |
45 | renderObjects = false; | |
46 | // Only used if objects are rendered | |
47 | @Input() | |
48 | appendParentKey = true; | |
49 | @Input() | |
50 | hideEmpty = false; | |
51 | ||
52 | // If set, the classAddingTpl is used to enable different css for different values | |
53 | @Input() | |
54 | customCss?: { [css: string]: number | string | ((any) => boolean) }; | |
55 | ||
56 | columns: Array<CdTableColumn> = []; | |
92f5a8d4 | 57 | tableData: KeyValueItem[]; |
11fdf7f2 TL |
58 | |
59 | /** | |
60 | * The function that will be called to update the input data. | |
61 | */ | |
62 | @Output() | |
63 | fetchData = new EventEmitter(); | |
64 | ||
81eedcae | 65 | constructor(private datePipe: CdDatePipe) {} |
11fdf7f2 TL |
66 | |
67 | ngOnInit() { | |
68 | this.columns = [ | |
69 | { | |
70 | prop: 'key', | |
71 | flexGrow: 1, | |
72 | cellTransformation: CellTemplate.bold | |
73 | }, | |
74 | { | |
75 | prop: 'value', | |
76 | flexGrow: 3 | |
77 | } | |
78 | ]; | |
79 | if (this.customCss) { | |
80 | this.columns[1].cellTransformation = CellTemplate.classAdding; | |
81 | } | |
82 | // We need to subscribe the 'fetchData' event here and not in the | |
83 | // HTML template, otherwise the data table will display the loading | |
84 | // indicator infinitely if data is only bound via '[data]="xyz"'. | |
85 | // See for 'loadingIndicator' in 'TableComponent::ngOnInit()'. | |
86 | if (this.fetchData.observers.length > 0) { | |
87 | this.table.fetchData.subscribe(() => { | |
88 | // Forward event triggered by the 'cd-table' data table. | |
89 | this.fetchData.emit(); | |
90 | }); | |
91 | } | |
92 | this.useData(); | |
93 | } | |
94 | ||
95 | ngOnChanges() { | |
96 | this.useData(); | |
97 | } | |
98 | ||
99 | useData() { | |
100 | if (!this.data) { | |
101 | return; // Wait for data | |
102 | } | |
92f5a8d4 | 103 | this.tableData = this.makePairs(this.data); |
11fdf7f2 TL |
104 | } |
105 | ||
92f5a8d4 TL |
106 | private makePairs(data: any): KeyValueItem[] { |
107 | let result: KeyValueItem[] = []; | |
11fdf7f2 TL |
108 | if (!data) { |
109 | return; // Wait for data | |
110 | } else if (_.isArray(data)) { | |
92f5a8d4 | 111 | result = this.makePairsFromArray(data); |
11fdf7f2 | 112 | } else if (_.isObject(data)) { |
92f5a8d4 | 113 | result = this.makePairsFromObject(data); |
11fdf7f2 TL |
114 | } else { |
115 | throw new Error('Wrong data format'); | |
116 | } | |
92f5a8d4 TL |
117 | result = result |
118 | .map((item) => { | |
119 | item.value = this.convertValue(item.value); | |
120 | return item; | |
121 | }) | |
122 | .filter((i) => i.value !== null); | |
123 | return _.sortBy(this.renderObjects ? this.insertFlattenObjects(result) : result, 'key'); | |
11fdf7f2 TL |
124 | } |
125 | ||
92f5a8d4 | 126 | private makePairsFromArray(data: any[]): KeyValueItem[] { |
11fdf7f2 TL |
127 | let temp = []; |
128 | const first = data[0]; | |
129 | if (_.isArray(first)) { | |
130 | if (first.length === 2) { | |
131 | temp = data.map((a) => ({ | |
132 | key: a[0], | |
133 | value: a[1] | |
134 | })); | |
135 | } else { | |
92f5a8d4 TL |
136 | throw new Error( |
137 | `Array contains too many elements (${first.length}). ` + | |
138 | `Needs to be of type [string, any][]` | |
139 | ); | |
11fdf7f2 TL |
140 | } |
141 | } else if (_.isObject(first)) { | |
142 | if (_.has(first, 'key') && _.has(first, 'value')) { | |
143 | temp = [...data]; | |
144 | } else { | |
145 | temp = data.reduce( | |
92f5a8d4 | 146 | (previous: any[], item) => previous.concat(this.makePairsFromObject(item)), |
11fdf7f2 TL |
147 | temp |
148 | ); | |
149 | } | |
150 | } | |
151 | return temp; | |
152 | } | |
153 | ||
92f5a8d4 | 154 | private makePairsFromObject(data: object): KeyValueItem[] { |
11fdf7f2 TL |
155 | return Object.keys(data).map((k) => ({ |
156 | key: k, | |
157 | value: data[k] | |
158 | })); | |
159 | } | |
160 | ||
92f5a8d4 | 161 | private insertFlattenObjects(data: KeyValueItem[]): any[] { |
11fdf7f2 | 162 | return _.flattenDeep( |
92f5a8d4 | 163 | data.map((item) => { |
11fdf7f2 TL |
164 | const value = item.value; |
165 | const isObject = _.isObject(value); | |
166 | if (!isObject || _.isEmpty(value)) { | |
167 | if (isObject) { | |
168 | item.value = ''; | |
169 | } | |
170 | return item; | |
171 | } | |
172 | return this.splitItemIntoItems(item); | |
173 | }) | |
174 | ); | |
175 | } | |
176 | ||
177 | /** | |
178 | * Split item into items will call _makePairs inside _makePairs (recursion), in oder to split | |
179 | * the object item up into items as planned. | |
180 | */ | |
92f5a8d4 TL |
181 | private splitItemIntoItems(data: { key: string; value: object }): KeyValueItem[] { |
182 | return this.makePairs(data.value).map((item) => { | |
11fdf7f2 | 183 | if (this.appendParentKey) { |
92f5a8d4 | 184 | item.key = data.key + ' ' + item.key; |
11fdf7f2 TL |
185 | } |
186 | return item; | |
187 | }); | |
188 | } | |
189 | ||
92f5a8d4 TL |
190 | private convertValue(value: any): KeyValueItem { |
191 | if (_.isArray(value)) { | |
192 | if (_.isEmpty(value) && this.hideEmpty) { | |
193 | return null; | |
194 | } | |
195 | value = value.map((item) => (_.isObject(item) ? JSON.stringify(item) : item)).join(', '); | |
196 | } else if (_.isObject(value)) { | |
197 | if ((this.hideEmpty && _.isEmpty(value)) || !this.renderObjects) { | |
198 | return null; | |
199 | } | |
200 | } else if (_.isString(value)) { | |
201 | if (value === '' && this.hideEmpty) { | |
202 | return null; | |
203 | } | |
204 | if (this.isDate(value)) { | |
205 | value = this.datePipe.transform(value) || value; | |
206 | } | |
81eedcae | 207 | } |
81eedcae | 208 | |
92f5a8d4 | 209 | return value; |
81eedcae TL |
210 | } |
211 | ||
212 | private isDate(s) { | |
213 | const sep = '[ -:.TZ]'; | |
214 | const n = '\\d{2}' + sep; | |
215 | // year - m - d - h : m : s . someRest Z (if UTC) | |
216 | return s.match(new RegExp('^\\d{4}' + sep + n + n + n + n + n + '\\d*' + 'Z?$')); | |
217 | } | |
11fdf7f2 | 218 | } |