pragma-views2
Version:
506 lines (418 loc) • 15 kB
JavaScript
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);