UNPKG

pragma-views2

Version:

506 lines (418 loc) 15 kB
import {HierarchicalBase} from "../lib/hierarchical-base.js"; import {GridShortcuts} from "../lib/grid-shortcuts.js"; import {inflateTemplate} from '../../baremetal/lib/template-inflator.js' import {setAttribute} from '../../baremetal/lib/template-inflator.js' class PragmaGrid extends HierarchicalBase { /** * Removes the component ui elements from the DOM * @private */ _cleanupUI() { this.removeChildren(this.body); } /** * Collapses a grouped row by setting the children as hidden * @param {*} row */ _collapseGroupedRow(row) { const children = this._getChildrenGroupsByParentKey(row.dataset.key); row.setAttribute("aria-expanded", "false"); for (let child of children) { if (child.hasAttribute("aria-expanded")) { setAttribute(child, {"aria-expanded": "false"}); } setAttribute(child, {"aria-hidden": "true"}); } this._updateVisibleChildren(); } /** * Creates the table row element * If the item has children, create a grouped row * otherwise create a leaf row */ _createElement(item) { return item.hasChildren ? this._createGroupedItemRow(item) : this._createLeafItem(item); } /** * Creates a row for a an item that has children * @param {*} item */ _createGroupedItemRow(item) { const template = this._templates.get(this._groupTemplate); const cloned = template.cloneNode(true); const inflated = inflateTemplate(cloned, item); const td = inflated.querySelector("td"); const div = inflated.querySelector(".grouped-row-info"); const row = inflated.querySelector("tr"); td.colSpan = this._nrOfColumns; row.dataset.id = item.model[this.idField]; row.dataset.index = item.model.index; row.dataset.depth = item.depth; row.setAttribute("aria-expanded", false); this._setRowPadding(div, item.depth); return row; } _setRowPadding(div, depth) { div.style.paddingLeft = `${this._depthSpacing * (depth - 1)}rem`; } /** * Creates the table header element */ _createHeader() { const fragment = document.createDocumentFragment(); const gridCols = this.querySelectorAll("pragma-grid-column"); this._columns = Array.from(gridCols); this._nrOfColumns = this._columns.length + 1; // adding an additional column for the checkbox for (let column of this._columns) { const th = document.createElement("th"); th.textContent = column.title; if (column.dataset.width != undefined) { th.style.width = `${column.dataset.width}px`; } fragment.appendChild(th); } this._header.appendChild(fragment); } /** * Creates a row for an item that has no further children * @param {*} item */ _createLeafItem(item) { const model = this.isHierarchical && item.model ? item.model : item; const template = this._templates.get(this._leafTemplate); const cloned = template.cloneNode(true); const row = cloned.querySelector("tr"); row.dataset.id = model[this.idField]; row.dataset.index = model.index; row.dataset.depth = model.depth; setAttribute(row, {role: "row"}); this._columns.forEach(col => { const cell = document.createElement("td"); const span = document.createElement("span"); const field = col.getAttribute("field"); setAttribute(cell, {"role": "gridcell", "tabindex": "-1"}); span.textContent = model[field]; cell.appendChild(span) row.appendChild(cell); }); // Checking if the item is contained in selectedId const selected = this.isSelected(model[this.idField]); if (selected === true) { this._selectNode(row, selected); } return row; } /** * Expands a grouped row and requests the next batch of data * @param {*} row */ _expandGroupedRow(row) { if (row.dataset.loaded === "true") { const children = this._getChildrenByParentKey(row.dataset.key); for (let child of children) { setAttribute(child, {"aria-hidden": "false"}); } setAttribute(row, {"aria-expanded": "true"}); this._updateVisibleChildren(); } else { row.setAttribute("aria-expanded", true); const key = row.dataset.key; const result = this._items.get(key); const requestData = { id: key, dataset: result.items, size: this._batchSize, page: 0 }; this.requestBatch(requestData); } } _updateVisibleChildren() { if (this._shortcuts != null) { this._shortcuts.updateChildren(); } } /** * * @param parentId * @returns {any[]} * @private */ _getChildrenByParentKey(parentId) { const arr = this.body.querySelectorAll(`[data-parent-key='${parentId}']`); return Array.from(arr); } /** * * @param parentId * @returns {any[]} * @private */ _getChildrenGroupsByParentKey(parentId) { const arr = this.body.querySelectorAll(`[data-parent-key*='${parentId}'][aria-hidden='false']`); return Array.from(arr); } /** * Focus event handler * Initialises the keyboard shortcuts for the component * @private */ _focus() { if (this._shortcuts == null) { this._shortcuts = new GridShortcuts(this); this._shortcuts.initialise(); } } click(event) { this._handleTargetEvent(event); } /** * Double Click handler. Invoked when the an item is * double clicked on the grid. Selects the appropriate * action based on the tag double clicked. * @param {*} event */ doubleClick(event) { if (event.target.tagName == "TD" && event.target.parentNode.classList.contains("leaf-row")) { this.dispatchCustomEvent("doubleclick", event.target); } } _handleTargetEvent(event) { const target = event.target; const tag = target.tagName.toLowerCase(); const action = this._clickEventMap.get(tag); if (action != undefined) { action.apply(this, [target]); } } _getRowElementByKey(id) { return this.querySelector(`[data-key="${id}"]`); } /** * * @param {*} row * @param {*} checked */ _handleChildSelection(row, checked) { const key = row.dataset.key; if (row.classList.contains("grouped-row")) { const children = this._getChildrenByParentKey(key); for (let child of children) { this._handleChildSelection(child, checked); } } this._selectNode(row, checked); } /*** * Click event handler for the checbox on the grid * @param event * @private */ _inputClicked(event) { const tr = event.closest("tr"); const checked = event.checked; if (event.parentElement.classList.contains("row-selection")) { return this._selectAllRows(checked); } if (tr.classList.contains("grouped-row")) { return this._handleChildSelection(tr, checked); } this._selectNode(tr, checked); } _insertFragment(parentKey, parentRow, fragment) { if (parentKey === "root") { this.body.appendChild(fragment); } else { parentRow.dataset.loaded = "true"; parentRow.parentNode.insertBefore(fragment, parentRow.nextSibling); } } /** * Sets the selection of the row * and adds / removes it from selected items array * @param {*} row * @param {*} checked */ _selectNode(row, checked) { const input = row.querySelector("input"); const isGroup = row.hasAttribute("aria-expanded"); setAttribute(row, {"aria-selected": checked}); input.checked = checked; if (isGroup == false) { const id = Number(row.dataset.id); this.setSelectedId(id, checked); } } /** * Selects all the rows in the grid * @param checked */ _selectAllRows(checked) { const rows = this.body.querySelectorAll("tr"); for (let row of rows) { this._selectNode(row, checked); } } /** * Toggles expansion of the current grouped * item row. * @param {*} target */ _toggleGroupedItemRow(target) { const row = target.closest("tr"); const expanded = row.getAttribute("aria-expanded"); if (expanded === "false") { this._expandGroupedRow(row); } else { this._collapseGroupedRow(row); } } _clearSelection() { const items = this.querySelectorAll("[aria-selected='true']"); for (let item of items) { const input = item.querySelector("input"); if (input != null) { input.checked = false; setAttribute(item, {"aria-selected": "false"}); } } this.clearSelectedId(); } /** * Toggles selection of the row target * @param {*} target */ _toggleSelectedRow(target) { const row = target.closest("tr"); this._clearSelection(); this._selectNode(row, true); } /** * Creates an element for each item in the collection * and inserts it into the DOM. Checks if the content * should be inserted below its parent element. * @param data */ updateUI(data) { requestAnimationFrame(() => { const fragment = document.createDocumentFragment(); let parentRow = this._getRowElementByKey(data.id); // Check if user collapsed parent to not show rows when batch worker still running const hidden = parentRow != undefined && parentRow.getAttribute("aria-expanded") == "false"; for (let item of data.items) { const newRow = this._createElement(item); newRow.setAttribute("aria-hidden", hidden); let key; if (parentRow != null) { newRow.dataset.parentKey = data.id; key = `${data.id}_${item.model.index}`; const selected = parentRow.getAttribute("aria-selected") === "true"; if (selected === true) { this._selectNode(newRow, selected); } } else { if (this.isHierarchical) { key = item.model.index; } else { key = item.id; } } newRow.dataset.key = key; this._items.set(key.toString(), item); fragment.appendChild(newRow); } this._insertFragment(data.id, parentRow, fragment); this.requestBatch(data); this._updateVisibleChildren(); }); } _validateComponent() { if (this.idField == null) { throw new Error("Id field is required"); } } /** * Loads the templates for the component and caches them * @private */ _loadTemplates() { const group = document.importNode(window.templates.get("pragma-grid-grouped-row-template"), true); const leaf = document.importNode(window.templates.get("pragma-grid-leaf-row-template"), true); this._groupTemplate = "group-template"; this._leafTemplate = "leaf-template"; this._templates = new Map(); this._templates.set(this._groupTemplate, group); this._templates.set(this._leafTemplate, leaf); } /** * Click handler. Invoked when the an item is * clicked on the grid. Selects the appropriate * action based on the tag clicked. * @param {*} event */ click(event) { const target = event.target; const tag = target.tagName.toLowerCase(); const action = this._clickEventMap.get(tag); if (action != null) { action.apply(this, [target]); } } connectedCallback() { super.connectedCallback(); this._validateComponent(); this._loadTemplates(); this.initSelectionMode(); this.batchSize = 1000; this._focusHandler = this._focus.bind(this); this._clickEventMap = new Map([ ["input", this._inputClicked], ["icon-button", this._toggleGroupedItemRow], ["td", this._toggleSelectedRow], ["span", this._toggleSelectedRow] ]); this.setAttribute("tabindex", "0"); this.addEventListener("focus", this._focusHandler); this._items = new Map(); } disconnectedCallback() { super.disconnectedCallback(); this.removeEventListener("focus", this._focusHandler); this._cleanupUI(); this._focusHandler = null; this._header = null; this.body = null; this._table = null; this._columns = null; this._clickEventMap = null; this._templates = null; this._leafTemplate = null; this._groupTemplate = null; this._items = null; if (this._shortcuts != null) { this._shortcuts.dispose(); this._shortcuts = null; } } clear() { this._cleanupUI(); } /** * Call to initialise the component template */ initTemplate() { const instance = document.importNode(window.templates.get("pragma-grid"), true); this.appendChild(instance); this._header = this.querySelector("#pragma-grid-header"); this.body = this.querySelector("#pragma-grid-body"); this._depthSpacing = 1; this._createHeader(); } } customElements.define('pragma-grid', PragmaGrid);