import {
remove as removeFromArray,
} from "js/Array.js";
import {
getOrCreate as getOrCreateInMap,
} from "js/Map.js";
import {
removeChildren,
} from "html/Node.js";
import Table from "html/Table/Table.js";
export default class Row {
constructor(data) {
console.assert(typeof data === "object", data);
this._data = data;
this._cells = new Map;
this._selected = false;
this._expanded = false;
this._table = null;
this._depth = NaN;
this._parent = null;
this._childRows = [];
this._childRowsElement = null;
this._element = document.createElement("li");
this._element.addEventListener("mousedown", this._handleMouseDown.bind(this));
}
// Public
hasData(columnIdentifier) {
return columnIdentifier in this._data;
}
getData(columnIdentifier) {
return this._data[columnIdentifier];
}
setData(columnIdentifier, value) {
if (value)
this._data[columnIdentifier] = value;
else
delete this._data[columnIdentifier];
this._createCell(columnIdentifier);
}
addChildRow(newChildRow) {
console.assert(newChildRow instanceof Row, newChildRow, this);
console.assert(!this._childRows.includes(newChildRow), newChildRow, this);
console.assert(! newChildRow._parent, newChildRow, this);
newChildRow._parent = this;
this._childRows.push(newChildRow);
this._element.classList.add("parent");
if (this._childRowsElement) {
console.assert(this._table instanceof Table, this);
console.assert(!isNaN(this._depth), this);
this._childRowsElement.appendChild(newChildRow.attach(this._table, this._depth + 1));
} else if (this._table)
this._element.parentNode.insertBefore(this._getOrCreateChildRowsElement(), this._element.nextSibling);
}
removeChildRow(existingChildRow) {
console.assert(existingChildRow instanceof Row, existingChildRow, this);
console.assert(this._childRows.includes(existingChildRow), existingChildRow, this);
console.assert(existingChildRow._parent === this, existingChildRow, this);
existingChildRow._parent = null;
removeFromArray(this._childRows, existingChildRow);
if (this._table)
existingChildRow.detach(this._table);
if (this._childRows.length)
return;
this._element.classList.remove("parent");
if (this._childRowsElement) {
this._childRowsElement.remove();
this._childRowsElement = null;
}
}
get revealed() {
if (!this._table)
return false;
let parent = this;
while (parent = parent._parent) {
if (!parent._expanded)
return false;
}
return true;
}
get selected() { return this._selected; }
set selected(selected) {
console.assert(this._table instanceof Table && typeof this._table.rowSelected === "function", this);
if (this._selected === selected)
return;
this._selected = !!selected;
this._element.classList.toggle("selected", this._selected);
if (this._selected) {
if (this._element.scrollIntoViewIfNeeded)
this._element.scrollIntoViewIfNeeded(false);
this._table.rowSelected(this);
}
}
get expanded() { return this._expanded; }
expand() {
if (!this._childRows.length)
return;
if (this._expanded)
return;
this._expanded = true;
this._element.classList.add("expanded");
}
collapse() {
if (!this._childRows.length)
return;
if (!this._expanded)
return;
this._expanded = false;
this._element.classList.remove("expanded");
}
get parent() { return this._parent; }
previousSiblingRow() {
console.assert(this._table instanceof Table && typeof this._table.rowPreviousTopLevelRow === "function", this);
if (!this._parent)
return this._table.rowPreviousTopLevelRow(this);
let index = this._parent._childRows.indexOf(this);
return this._parent._childRows[index - 1] || null;
}
nextSiblingRow() {
console.assert(this._table instanceof Table && typeof this._table.rowNextTopLevelRow === "function", this);
if (!this._parent)
return this._table.rowNextTopLevelRow(this);
let index = this._parent._childRows.indexOf(this);
return this._parent._childRows[index + 1] || null;
}
previousRevealedRow(options = {}) {
let row = this;
do {
let previousRow = row.previousSiblingRow(options);
if (previousRow) {
row = previousRow;
while (row && row._childRows && row._expanded && row.revealed)
row = row._childRows[row._childRows.length - 1];
} else
row = row._parent;
else
row = null;
} while (row && !row.revealed);
return row;
}
nextRevealedRow(options = {}) {
let row = this;
do {
if (row._childRows && row._expanded)
row = row._childRows[0];
else {
while (row && row._parent && !row.nextSiblingRow(options))
row = row._parent;
if (row)
row = row.nextSiblingRow(options);
}
} while (row && !row.revealed);
return row;
}
// Protected
attach(table, depth = 0) {
console.assert(!this._table, this);
console.assert(isNaN(this._depth), this);
console.assert(table instanceof Table, table, this);
this._table = table;
console.assert(!isNaN(depth), table, depth, this);
this._depth = depth;
console.assert(typeof this._table.rowColumns === "function", this);
console.assert(typeof this._table.rowSelected === "function", this);
console.assert(typeof this._table.rowPreviousTopLevelRow === "function", this);
console.assert(typeof this._table.rowNextTopLevelRow === "function", this);
for (let column of this._table.rowColumns(this))
this._createCell(column.identifier);
let fragment = document.createDocumentFragment();
fragment.appendChild(this._element);
if (this._childRows.length)
fragment.appendChild(this._getOrCreateChildRowsElement());
return fragment;
}
detach(table) {
console.assert(table instanceof Table, table, this);
console.assert(table === this._table, table, this);
console.assert(!isNaN(this._depth), this);
this._table = null;
this._depth = NaN;
this._element.remove();
if (this._childRowsElement) {
this._childRowsElement.remove();
this._childRowsElement = null;
}
for (let child of this._childRows)
child.detach(table);
}
// Private
_createCell(columnIdentifier) {
let cell = getOrCreateInMap(this._cells, columnIdentifier, In=> {
let cellElement = this._element.appendChild(document.createElement("span"));
cellElement.classList.add("cell", columnIdentifier);
if (this._childRows.length && cellElement === this._element.children[0]) {
let disclosure = cellElement.appendChild(document.createElement("span"));
disclosure.className = "disclosure";
disclosure.addEventListener("click", this._handleDisclosureClick.bind(this));
}
let dataElement = cellElement.appendChild(document.createElement("span"));
dataElement.className = "data";
return dataElement;
});
removeChildren(cell);
if (this.hasData(columnIdentifier))
cell.append(this.getData(columnIdentifier));
}
_getOrCreateChildRowsElement() {
console.assert(this._childRows.length, this);
console.assert(this._table instanceof Table, this);
console.assert(!isNaN(this._depth), this);
if (!this._childRowsElement) {
this._childRowsElement = document.createElement("ul");
this._childRowsElement.style.setProperty("--table-row-depth", this._depth + 1);
for (let child of this._childRows)
this._childRowsElement.appendChild(child.attach(this._table, this._depth + 1));
}
return this._childRowsElement;
}
_handleMouseDown(event) {
if (event.target.classList.contains("disclosure"))
return;
this.selected = true;
}
_handleDisclosureClick(event) {
if (this._expanded)
this.collapse();
else
this.expand();
}
};