Skip to content

Commit 0993678

Browse files
authored
feat: added collapse feature in generic table component
1 parent 5e6e0dd commit 0993678

11 files changed

Lines changed: 190 additions & 26 deletions

File tree

lib/public/Model.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@ export default class Model extends Observable {
2929
/**
3030
* Load all sub-models and bind event handlers
3131
* @param {Object} HyperMD The hyperMD object passed from HTML code
32+
* @param {Object} window The window object from the HTML
33+
* @param {Object} document The document object
3234
* @param {Object} CompleteEmoji The CompleteEmoji object passed from HTML code
3335
*/
34-
constructor(HyperMD, CompleteEmoji) {
36+
constructor(HyperMD, window, document, CompleteEmoji) {
3537
super();
36-
// Bind HyperMD
38+
// Bind HyperMD, window and document
3739
this.HyperMD = HyperMD;
40+
this.document = document;
41+
this.window = window;
3842
this.CompleteEmoji = CompleteEmoji;
3943

4044
this.session = sessionService.get();
@@ -58,6 +62,7 @@ export default class Model extends Observable {
5862
this.router.bubbleTo(this);
5963

6064
this.handleLocationChange(); // Init first page
65+
this.window.addEventListener('resize', () => this.notify());
6166
}
6267

6368
/**

lib/public/app.css

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,27 @@ html, body {
22
scroll-behavior: smooth;
33
}
44

5-
.cell-xs { width: 2rem; }
6-
.cell-s { width: 4rem; }
7-
.cell-m { width: 8rem; }
8-
.cell-l { width: 16rem; }
9-
.cell-xl { width: 32rem; }
5+
.cell-xs { max-width: 2rem; }
6+
.cell-s { max-width: 4rem; }
7+
.cell-m { max-width: 8rem; }
8+
.cell-l { max-width: 16rem; }
9+
.cell-xl { max-width: 32rem; }
10+
11+
.overflow {
12+
height: 1.5rem;
13+
overflow: hidden;
14+
text-overflow: ellipsis;
15+
word-break: break-all;
16+
}
17+
18+
.show-overflow {
19+
height: initial;
20+
word-break: normal;
21+
}
22+
23+
.collapse-button {
24+
margin-left: 0.5rem;
25+
}
1026

1127
/* last column fills space */
1228
.cell-xs:last-child, .cell-s:last-child, .cell-m:last-child, .cell-l:last-child, .cell-xl:last-child { width: initial; }

lib/public/components/Table/content.js

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,71 @@
1111
* or submit itself to any jurisdiction.
1212
*/
1313

14-
import { h } from '/js/src/index.js';
14+
import { h, iconPlus, iconMinus } from '/js/src/index.js';
1515

1616
/**
1717
* Renders a single row with content
1818
* @param {Object} value The key value potentially containing a formatter function
1919
* @param {String} text The content text
20+
* @param {String} rowId The current rowId
21+
* @param {Object} model The global model object
2022
* @return {vnode} A single table cell containing (formatted) text
2123
*/
22-
const row = (value, text) => {
23-
const formatted = value && value.format ? value.format(text) : text;
24-
return h('td#', formatted);
24+
const row = (value, text, rowId, model) => {
25+
const formattedText = value && value.format ? value.format(text) : text;
26+
27+
const columnId = `${rowId}-${value.name.toLowerCase()}`;
28+
const canExpand = model.logs.canColumnExpand(rowId, value.name);
29+
const isExpanded = model.logs.isColumnExpanded(rowId, value.name);
30+
const base = `.${value.size}#${columnId}`;
31+
32+
return h(`td${base}`, {
33+
onupdate: () => {
34+
if (value.expand) {
35+
const element = model.document.getElementById(`${columnId}-text`);
36+
const minimalHeight = model.logs.getMinimalColumnHeight(rowId, value.name);
37+
const shouldCollapse = element.scrollHeight > minimalHeight ||
38+
element.scrollHeight > element.offsetHeight;
39+
if (!canExpand && shouldCollapse) {
40+
model.logs.addCollapsableColumn(rowId, value.name, element.offsetHeight);
41+
} else if (canExpand && element.scrollHeight <= minimalHeight) {
42+
model.logs.disableCollapsableColumn(rowId, value.name);
43+
}
44+
}
45+
},
46+
onclick: canExpand ? (e) => e.stopPropagation() : null,
47+
}, [
48+
h('.flex-row.items-center', [
49+
h(`div#${columnId}-text.overflow${isExpanded ? '.show-overflow' : ''}`, formattedText),
50+
value.expand && canExpand &&
51+
h(`#${columnId}-${!isExpanded ? 'plus' : 'minus'}.${isExpanded ? 'danger' : 'primary'}`, {
52+
onclick: (e) => {
53+
e.stopPropagation();
54+
model.logs.toggleCollapse(rowId, value.name);
55+
},
56+
}, isExpanded
57+
? h('.collapse-button', iconMinus())
58+
: h('.flex-row', [h('.black', '...'), h('.collapse-button', iconPlus())])),
59+
]),
60+
]);
2561
};
2662

2763
/**
2864
* Renders a list of rows with content
2965
* @param {Array} data The full collection of API data corresponding to the keys
3066
* @param {Object} keys The full collection of API keys and their corresponding header values
3167
* @param {Function} params Additional element parameters, wrapped in a function
68+
* @param {Object} model The global model object
3269
* @return {vnode} A filled array of rows based on the given data and keys
3370
*/
34-
const content = (data, keys, params) => {
71+
const content = (data, keys, params, model) => {
3572
const idKey = Object.keys(keys).find((key) => keys[key] && keys[key].primary);
36-
return h('tbody', data.map((entry) =>
37-
h(`tr#row${entry[idKey]}`, params(entry), Object.entries(keys).map(([key, value]) => row(value, entry[key])))));
73+
74+
return h('tbody', data.map((entry) => {
75+
const rowId = `row${entry[idKey]}`;
76+
return h(`tr#${rowId}`, params(entry), Object.entries(keys)
77+
.map(([key, value]) => row(value, entry[key], rowId, model)));
78+
}));
3879
};
3980

4081
export default content;

lib/public/components/Table/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ const filterKeysByVisibility = (keys) => {
3333
* @param {Array} data An object array, with every object representing a to be rendered row
3434
* @param {Object} keys The full collection of API keys and their corresponding header values
3535
* @param {Function} params Additional element parameters, wrapped in a function
36+
* @param {Object} model The global model object
3637
* @returns {vnode} Return the total view of the table to rendered
3738
*/
38-
const table = (data, keys, params = () => null) => {
39+
const table = (data, keys, params = () => null, model) => {
3940
filterKeysByVisibility(keys);
4041
return h('table.table.table-hover.shadow-level1', [
4142
headers(keys),
42-
content(data, keys, params),
43+
content(data, keys, params, model),
4344
]);
4445
};
4546

lib/public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
import Model from './Model.js';
9393

9494
// Start application
95-
const model = new Model(HyperMD, CompleteEmoji);
95+
const model = new Model(HyperMD, window, document, CompleteEmoji);
9696
const debug = true; // shows when redraw is done
9797
mount(document.body, view, model, debug);
9898

lib/public/views/Logs/ActiveColumns/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const ACTIVE_COLUMNS = {
1616
name: 'Title',
1717
visible: true,
1818
size: 'cell-l',
19+
expand: true,
1920
},
2021
id: {
2122
name: 'Entry ID',

lib/public/views/Logs/Logs.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export default class Overview extends Observable {
3535

3636
this.isPreviewActive = false;
3737
this.editors = [];
38+
39+
this.collapsableColumns = [];
40+
this.collapsedColumns = [];
3841
}
3942

4043
/**
@@ -341,6 +344,7 @@ export default class Overview extends Observable {
341344
this.filterTags = [];
342345
this.filterOperation = 'AND';
343346
this.moreFilters = false;
347+
this.collapsedColumns = [];
344348

345349
this.amountDropdownVisible = false;
346350
this.logsPerPage = 10;
@@ -369,6 +373,8 @@ export default class Overview extends Observable {
369373
this.title = '';
370374
this.editor = null;
371375
this.editors = [];
376+
this.collapsedColumns = [];
377+
this.isCollapsed = false;
372378
}
373379

374380
/**
@@ -396,4 +402,82 @@ export default class Overview extends Observable {
396402
this.isPreviewActive = !this.isPreviewActive;
397403
this.notify();
398404
}
405+
406+
/**
407+
* Add eligble columns for collapse to the array in the model
408+
* @param {String} rowId The rowId being collapsed
409+
* @param {String} name The name of the column to be collapsed
410+
* @param {Integer} height Minimal height of the column
411+
* @returns {undefined}
412+
*/
413+
addCollapsableColumn(rowId, name, height) {
414+
const existingColumn = this.collapsableColumns
415+
.find((element) => element.rowId === rowId && element.name === name);
416+
if (existingColumn) {
417+
existingColumn.disabled = false;
418+
} else {
419+
this.collapsableColumns.push({ rowId, name, height, disabled: false });
420+
}
421+
this.notify();
422+
}
423+
424+
/**
425+
* Remove eligble columns from the collapse array
426+
* @param {String} rowId The rowId being collapsed
427+
* @param {String} name The name of the column to be collapsed
428+
* @returns {undefined}
429+
*/
430+
disableCollapsableColumn(rowId, name) {
431+
this.collapsableColumns
432+
.find((element) => element.rowId === rowId && element.name === name).disabled = true;
433+
this.notify();
434+
}
435+
436+
/**
437+
* Toggle the collapse of a column
438+
* @param {String} rowId The rowId being collapsed
439+
* @param {String} name The name of the column to be collapsed
440+
* @returns {undefined}
441+
*/
442+
toggleCollapse(rowId, name) {
443+
if (this.isColumnExpanded(rowId, name)) {
444+
this.collapsedColumns = this.collapsedColumns
445+
.filter((element) => !(element.rowId === rowId && element.name === name));
446+
} else {
447+
this.collapsedColumns.push({ rowId, name });
448+
}
449+
450+
this.notify();
451+
}
452+
453+
/**
454+
* Returns wether the column should collapse or not
455+
* @param {String} rowId The rowId to be checked
456+
* @param {String} name The name of the column to be collapsed
457+
* @returns {Boolean} Returns wether the column in the row should collapse
458+
*/
459+
isColumnExpanded(rowId, name) {
460+
return this.collapsedColumns.some((entry) => entry.rowId === rowId && entry.name === name);
461+
}
462+
463+
/**
464+
* Returns wether the column should collapse or not
465+
* @param {String} rowId The rowId to be checked
466+
* @param {String} name The name of the column to be collapsed
467+
* @returns {Boolean} Returns wether the column in the row should collapse
468+
*/
469+
canColumnExpand(rowId, name) {
470+
return this.collapsableColumns.some((entry) => entry.rowId === rowId && entry.name === name && !entry.disabled);
471+
}
472+
473+
/**
474+
* Returns the minimal height of a column
475+
* @param {String} rowId The rowId to be checked
476+
* @param {String} name The name of the column
477+
* @returns {Integer} The smallest known height of the specified column
478+
*/
479+
getMinimalColumnHeight(rowId, name) {
480+
const targetColumn = this.collapsableColumns.find((entry) => entry.rowId === rowId && entry.name === name);
481+
return targetColumn && targetColumn.height;
482+
}
399483
}

lib/public/views/Logs/Overview/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const logOverviewScreen = (model) => {
4545
table(data.isSuccess() ? data.payload : [], ACTIVE_COLUMNS, (entry) => ({
4646
style: 'cursor: pointer;',
4747
onclick: () => model.router.go(`?page=entry&id=${entry.id}`),
48-
})),
48+
}), model),
4949
h('.flex-row.justify-between.mv3', [
5050
h('.w-15', amountSelector(() =>
5151
model.logs.toggleLogsDropdownVisible(), (amount) =>

lib/public/views/Tags/Details/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const tagDetails = (model) => {
5656
cursor: 'pointer',
5757
},
5858
onclick: () => model.router.go(`?page=entry&id=${entry.id}`),
59-
})),
59+
}), model),
6060
Failure: (payload) => payload.map(errorAlert),
6161
}),
6262
},

lib/public/views/Tags/Overview/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const tagOverview = (model) => {
4747
Success: (payload) => table(payload, ACTIVE_COLUMNS, (entry) => ({
4848
style: 'cursor: pointer;',
4949
onclick: () => model.router.go(`?page=tag&id=${entry.id}`),
50-
})),
50+
}), model),
5151
Failure: (payload) => payload.map(errorAlert),
5252
}),
5353
];

0 commit comments

Comments
 (0)