@financial-times/o-table
Version:
Provides styling and behvaiour for tables across FT products.
113 lines (105 loc) • 4 kB
JavaScript
import BaseTable from './BaseTable.js';
class FlatTable extends BaseTable {
/**
* Sort the given table.
*
* @typedef TableSorter
* @access public
* @param {BaseTable} table - The table instance to sort.
* @param {number} columnIndex - The index of the table column to sort.
* @param {string} sortOrder - How to sort the column, "ascending" or "descending"
* @param {number} batch [100] - Deprecated. No longer used. How many rows to render at once when sorting.
* @returns {void}
*/
/**
* Initialises an `o-table` component with "flat" responsive behaviour.
*
* @access public
* @param {HTMLElement} rootEl - The `o-table` element.
* @param {TableSorter} sorter the TableSorter instance
* @param {object} opts [{}]
* @param {boolean} opts.sortable [true]
* @returns {FlatTable} The new flat table
*/
constructor(rootEl, sorter, opts = {}) {
super(rootEl, sorter, opts);
// Duplicate row headings before adding sort buttons.
this._tableHeadersWithoutSort = this.tableHeaders.map(header => header.cloneNode(true));
// Flat table can only work given headers.
if (this.tableHeaders.length <= 0) {
// eslint-disable-next-line no-console
console.warn('Could not create a "flat" table as no headers were found. Ensure table headers are placed within "<thead>". Removing class "o-table--responsive-flat".', rootEl);
rootEl.classList.remove('o-table--responsive-flat');
} else {
this._createFlatTableStructure();
}
// Defer other tasks.
window.setTimeout(this.addSortButtons.bind(this), 0);
window.setTimeout(this._ready.bind(this), 0);
return this;
}
/**
* Update the o-table instance with rows added dynamically to the table.
*
* @returns {void}
*/
updateRows() {
// Update new rows to support the flat structure.
const latestRows = this._getLatestRowNodes();
this._createFlatTableStructure(latestRows);
// Update row visibility, sort, etc.
super.updateRows();
}
/**
* Get all the table body's current row nodes, without nodes duplicated for
* the responsive "flat" style
*
* @returns {Array<Node>} all the trs
* @access private
*/
_getLatestRowNodes() {
return this.tbody ? Array.from(this.tbody.querySelectorAll('tr:not(.o-table__duplicate-row)')) : [];
}
/**
* Duplicate table headers for each data item.
* i.e. Each row is shown as a single item with its own headings.
*
* @param {Array<HTMLTableRowElement>} rows rows to duplicate
* @access private
*/
_createFlatTableStructure(rows = this.tableRows) {
rows
.filter(row => !row.hasAttribute('data-o-table-flat-headings')) // only process rows once
.forEach(row => {
const data = Array.from(row.getElementsByTagName('td'));
row.setAttribute('data-o-table-flat-headings', true);
window.requestAnimationFrame(() => {
// Create a new table body for every row.
const newGroupBody = document.createElement('tbody');
newGroupBody.classList.add('o-table__responsive-body');
// Append all the other rows as heading / value pairs.
this._tableHeadersWithoutSort.forEach((header, index) => {
// Create the row.
const newRow = document.createElement('tr');
newRow.classList.add('o-table__duplicate-row');
// Duplicate the original heading cell.
const clonedHeader = header.cloneNode(true);
clonedHeader.classList.add('o-table__duplicate-heading');
clonedHeader.setAttribute('scope', 'row');
clonedHeader.setAttribute('role', 'rowheader');
// Duplicate the original data cell.
const clonedTd = data[index].cloneNode(true);
// Append the header and data cell to the row.
newRow.appendChild(clonedHeader);
newRow.appendChild(clonedTd);
// Append the row to the body.
newGroupBody.appendChild(newRow);
});
// Append the new bodies, which represent each row on
// desktop, to the root element.
this.rootEl.appendChild(newGroupBody);
});
});
}
}
export default FlatTable;