Skip to content

Commit

Permalink
feat: added collapse feature in generic table component
Browse files Browse the repository at this point in the history
  • Loading branch information
john-ghatas authored Jun 25, 2020
1 parent 5e6e0dd commit 0993678
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 26 deletions.
9 changes: 7 additions & 2 deletions lib/public/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,16 @@ export default class Model extends Observable {
/**
* Load all sub-models and bind event handlers
* @param {Object} HyperMD The hyperMD object passed from HTML code
* @param {Object} window The window object from the HTML
* @param {Object} document The document object
* @param {Object} CompleteEmoji The CompleteEmoji object passed from HTML code
*/
constructor(HyperMD, CompleteEmoji) {
constructor(HyperMD, window, document, CompleteEmoji) {
super();
// Bind HyperMD
// Bind HyperMD, window and document
this.HyperMD = HyperMD;
this.document = document;
this.window = window;
this.CompleteEmoji = CompleteEmoji;

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

this.handleLocationChange(); // Init first page
this.window.addEventListener('resize', () => this.notify());
}

/**
Expand Down
26 changes: 21 additions & 5 deletions lib/public/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,27 @@ html, body {
scroll-behavior: smooth;
}

.cell-xs { width: 2rem; }
.cell-s { width: 4rem; }
.cell-m { width: 8rem; }
.cell-l { width: 16rem; }
.cell-xl { width: 32rem; }
.cell-xs { max-width: 2rem; }
.cell-s { max-width: 4rem; }
.cell-m { max-width: 8rem; }
.cell-l { max-width: 16rem; }
.cell-xl { max-width: 32rem; }

.overflow {
height: 1.5rem;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}

.show-overflow {
height: initial;
word-break: normal;
}

.collapse-button {
margin-left: 0.5rem;
}

/* last column fills space */
.cell-xs:last-child, .cell-s:last-child, .cell-m:last-child, .cell-l:last-child, .cell-xl:last-child { width: initial; }
Expand Down
55 changes: 48 additions & 7 deletions lib/public/components/Table/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,71 @@
* or submit itself to any jurisdiction.
*/

import { h } from '/js/src/index.js';
import { h, iconPlus, iconMinus } from '/js/src/index.js';

/**
* Renders a single row with content
* @param {Object} value The key value potentially containing a formatter function
* @param {String} text The content text
* @param {String} rowId The current rowId
* @param {Object} model The global model object
* @return {vnode} A single table cell containing (formatted) text
*/
const row = (value, text) => {
const formatted = value && value.format ? value.format(text) : text;
return h('td#', formatted);
const row = (value, text, rowId, model) => {
const formattedText = value && value.format ? value.format(text) : text;

const columnId = `${rowId}-${value.name.toLowerCase()}`;
const canExpand = model.logs.canColumnExpand(rowId, value.name);
const isExpanded = model.logs.isColumnExpanded(rowId, value.name);
const base = `.${value.size}#${columnId}`;

return h(`td${base}`, {
onupdate: () => {
if (value.expand) {
const element = model.document.getElementById(`${columnId}-text`);
const minimalHeight = model.logs.getMinimalColumnHeight(rowId, value.name);
const shouldCollapse = element.scrollHeight > minimalHeight ||
element.scrollHeight > element.offsetHeight;
if (!canExpand && shouldCollapse) {
model.logs.addCollapsableColumn(rowId, value.name, element.offsetHeight);
} else if (canExpand && element.scrollHeight <= minimalHeight) {
model.logs.disableCollapsableColumn(rowId, value.name);
}
}
},
onclick: canExpand ? (e) => e.stopPropagation() : null,
}, [
h('.flex-row.items-center', [
h(`div#${columnId}-text.overflow${isExpanded ? '.show-overflow' : ''}`, formattedText),
value.expand && canExpand &&
h(`#${columnId}-${!isExpanded ? 'plus' : 'minus'}.${isExpanded ? 'danger' : 'primary'}`, {
onclick: (e) => {
e.stopPropagation();
model.logs.toggleCollapse(rowId, value.name);
},
}, isExpanded
? h('.collapse-button', iconMinus())
: h('.flex-row', [h('.black', '...'), h('.collapse-button', iconPlus())])),
]),
]);
};

/**
* Renders a list of rows with content
* @param {Array} data The full collection of API data corresponding to the keys
* @param {Object} keys The full collection of API keys and their corresponding header values
* @param {Function} params Additional element parameters, wrapped in a function
* @param {Object} model The global model object
* @return {vnode} A filled array of rows based on the given data and keys
*/
const content = (data, keys, params) => {
const content = (data, keys, params, model) => {
const idKey = Object.keys(keys).find((key) => keys[key] && keys[key].primary);
return h('tbody', data.map((entry) =>
h(`tr#row${entry[idKey]}`, params(entry), Object.entries(keys).map(([key, value]) => row(value, entry[key])))));

return h('tbody', data.map((entry) => {
const rowId = `row${entry[idKey]}`;
return h(`tr#${rowId}`, params(entry), Object.entries(keys)
.map(([key, value]) => row(value, entry[key], rowId, model)));
}));
};

export default content;
5 changes: 3 additions & 2 deletions lib/public/components/Table/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ const filterKeysByVisibility = (keys) => {
* @param {Array} data An object array, with every object representing a to be rendered row
* @param {Object} keys The full collection of API keys and their corresponding header values
* @param {Function} params Additional element parameters, wrapped in a function
* @param {Object} model The global model object
* @returns {vnode} Return the total view of the table to rendered
*/
const table = (data, keys, params = () => null) => {
const table = (data, keys, params = () => null, model) => {
filterKeysByVisibility(keys);
return h('table.table.table-hover.shadow-level1', [
headers(keys),
content(data, keys, params),
content(data, keys, params, model),
]);
};

Expand Down
2 changes: 1 addition & 1 deletion lib/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
import Model from './Model.js';

// Start application
const model = new Model(HyperMD, CompleteEmoji);
const model = new Model(HyperMD, window, document, CompleteEmoji);
const debug = true; // shows when redraw is done
mount(document.body, view, model, debug);

Expand Down
1 change: 1 addition & 0 deletions lib/public/views/Logs/ActiveColumns/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const ACTIVE_COLUMNS = {
name: 'Title',
visible: true,
size: 'cell-l',
expand: true,
},
id: {
name: 'Entry ID',
Expand Down
84 changes: 84 additions & 0 deletions lib/public/views/Logs/Logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export default class Overview extends Observable {

this.isPreviewActive = false;
this.editors = [];

this.collapsableColumns = [];
this.collapsedColumns = [];
}

/**
Expand Down Expand Up @@ -341,6 +344,7 @@ export default class Overview extends Observable {
this.filterTags = [];
this.filterOperation = 'AND';
this.moreFilters = false;
this.collapsedColumns = [];

this.amountDropdownVisible = false;
this.logsPerPage = 10;
Expand Down Expand Up @@ -369,6 +373,8 @@ export default class Overview extends Observable {
this.title = '';
this.editor = null;
this.editors = [];
this.collapsedColumns = [];
this.isCollapsed = false;
}

/**
Expand Down Expand Up @@ -396,4 +402,82 @@ export default class Overview extends Observable {
this.isPreviewActive = !this.isPreviewActive;
this.notify();
}

/**
* Add eligble columns for collapse to the array in the model
* @param {String} rowId The rowId being collapsed
* @param {String} name The name of the column to be collapsed
* @param {Integer} height Minimal height of the column
* @returns {undefined}
*/
addCollapsableColumn(rowId, name, height) {
const existingColumn = this.collapsableColumns
.find((element) => element.rowId === rowId && element.name === name);
if (existingColumn) {
existingColumn.disabled = false;
} else {
this.collapsableColumns.push({ rowId, name, height, disabled: false });
}
this.notify();
}

/**
* Remove eligble columns from the collapse array
* @param {String} rowId The rowId being collapsed
* @param {String} name The name of the column to be collapsed
* @returns {undefined}
*/
disableCollapsableColumn(rowId, name) {
this.collapsableColumns
.find((element) => element.rowId === rowId && element.name === name).disabled = true;
this.notify();
}

/**
* Toggle the collapse of a column
* @param {String} rowId The rowId being collapsed
* @param {String} name The name of the column to be collapsed
* @returns {undefined}
*/
toggleCollapse(rowId, name) {
if (this.isColumnExpanded(rowId, name)) {
this.collapsedColumns = this.collapsedColumns
.filter((element) => !(element.rowId === rowId && element.name === name));
} else {
this.collapsedColumns.push({ rowId, name });
}

this.notify();
}

/**
* Returns wether the column should collapse or not
* @param {String} rowId The rowId to be checked
* @param {String} name The name of the column to be collapsed
* @returns {Boolean} Returns wether the column in the row should collapse
*/
isColumnExpanded(rowId, name) {
return this.collapsedColumns.some((entry) => entry.rowId === rowId && entry.name === name);
}

/**
* Returns wether the column should collapse or not
* @param {String} rowId The rowId to be checked
* @param {String} name The name of the column to be collapsed
* @returns {Boolean} Returns wether the column in the row should collapse
*/
canColumnExpand(rowId, name) {
return this.collapsableColumns.some((entry) => entry.rowId === rowId && entry.name === name && !entry.disabled);
}

/**
* Returns the minimal height of a column
* @param {String} rowId The rowId to be checked
* @param {String} name The name of the column
* @returns {Integer} The smallest known height of the specified column
*/
getMinimalColumnHeight(rowId, name) {
const targetColumn = this.collapsableColumns.find((entry) => entry.rowId === rowId && entry.name === name);
return targetColumn && targetColumn.height;
}
}
2 changes: 1 addition & 1 deletion lib/public/views/Logs/Overview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const logOverviewScreen = (model) => {
table(data.isSuccess() ? data.payload : [], ACTIVE_COLUMNS, (entry) => ({
style: 'cursor: pointer;',
onclick: () => model.router.go(`?page=entry&id=${entry.id}`),
})),
}), model),
h('.flex-row.justify-between.mv3', [
h('.w-15', amountSelector(() =>
model.logs.toggleLogsDropdownVisible(), (amount) =>
Expand Down
2 changes: 1 addition & 1 deletion lib/public/views/Tags/Details/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const tagDetails = (model) => {
cursor: 'pointer',
},
onclick: () => model.router.go(`?page=entry&id=${entry.id}`),
})),
}), model),
Failure: (payload) => payload.map(errorAlert),
}),
},
Expand Down
2 changes: 1 addition & 1 deletion lib/public/views/Tags/Overview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const tagOverview = (model) => {
Success: (payload) => table(payload, ACTIVE_COLUMNS, (entry) => ({
style: 'cursor: pointer;',
onclick: () => model.router.go(`?page=tag&id=${entry.id}`),
})),
}), model),
Failure: (payload) => payload.map(errorAlert),
}),
];
Expand Down
28 changes: 22 additions & 6 deletions test/public/logs/overview.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ module.exports = () => {
page.coverage.startJSCoverage({ resetOnNavigation: false }),
page.coverage.startCSSCoverage(),
]);
page.setViewport({
width: 700,
height: 720,
deviceScaleFactor: 1,
});

const { port } = server.address();
url = `http://localhost:${port}`;
Expand Down Expand Up @@ -273,7 +278,8 @@ module.exports = () => {
});

it('can create a log from the overview page', async () => {
const title = 'Test One';
const title = 'A very long title that should be collapsed in the overview screen!' +
'Adding some more text to it, does it have an ellipsis yet? I do not know!';
const text = 'Sample Text';

// Go back to the home page
Expand All @@ -292,7 +298,7 @@ module.exports = () => {
// Create the new log
const buttonSend = await page.$('button#send');
await buttonSend.evaluate((button) => button.click());
await page.waitFor(200);
await page.waitFor(250);

// Verify that the text from the first matches with the text posted and correct working of the redirect
// eslint-disable-next-line no-undef
Expand All @@ -305,7 +311,7 @@ module.exports = () => {
// Return the page to home
const buttonHame = await page.$('#home');
await buttonHame.evaluate((button) => button.click());
await page.waitFor(100);
await page.waitFor(150);

// Ensure you are at the overview page again
const doesTableExist = await page.$$('tr') ? true : false;
Expand All @@ -314,12 +320,22 @@ module.exports = () => {
// Get the latest post and verify the title of the log we posted
const table = await page.$$('tr');
firstRowId = await getFirstRow(table, page);
const firstRow = await page.$(`#${firstRowId}`);
const isTitleInRow = JSON.stringify(await page.evaluate((element) => element.innerText, firstRow))

const firstRow = await page.$(`#${firstRowId}-title-text`);

const isTitleInRow = JSON.stringify(await firstRow.evaluate((element) => element.innerText))
.includes(title);

// Verify the correct title is shown in the table
expect(isTitleInRow).to.equal(true);

// Collapse and de-collapse the opened title and verify the rendered text accordingly
const expandButton = await page.$(`#${firstRowId}-title-plus`);
await expandButton.evaluate((button) => button.click());
page.waitFor(100);

const collapseButton = await page.$(`#${firstRowId}-title-minus`);
await collapseButton.evaluate((button) => button.click());
page.waitFor(100);
});

it('notifies if table loading returned an error', async () => {
Expand Down

0 comments on commit 0993678

Please sign in to comment.