175 lines
5.5 KiB
JavaScript
175 lines
5.5 KiB
JavaScript
/**
|
|
* Very simple and fast tree grid/table, compatible with dynamic loading and jQuery fixedHeaderTable
|
|
* License: MPL 2.0+, (c) Vitaliy Filippov 2016
|
|
*/
|
|
|
|
/**
|
|
* USAGE:
|
|
*
|
|
* var TG = new TreeGrid(items, header);
|
|
* document.body.appendChild(TG.table);
|
|
*
|
|
* items: [ node={
|
|
* cells: [ 'html' or { innerHTML, style, className, title }, ... ],
|
|
* children: [ node... ],
|
|
* leaf: true/false,
|
|
* collapsed: true/false
|
|
* }, ... ]
|
|
*
|
|
* header: [ 'html' or { innerHTML, style, className, title }, ... ]
|
|
*/
|
|
function TreeGrid(items, header)
|
|
{
|
|
this.bindProps = { 'style': 1, 'className': 1, 'title': 1, 'innerHTML': 1 };
|
|
this.levelIndent = 20;
|
|
this.table = document.createElement('table');
|
|
this.table.className = 'grid';
|
|
this.thead = document.createElement('thead');
|
|
this.tbody = document.createElement('tbody');
|
|
this.table.appendChild(this.thead);
|
|
this.table.appendChild(this.tbody);
|
|
this.thead.appendChild(document.createElement('tr'));
|
|
this.setHeader(header);
|
|
new TreeGridNode({ children: items }, this);
|
|
}
|
|
|
|
TreeGrid.prototype.setHeader = function(newHeader)
|
|
{
|
|
var tr = this.thead.rows[0];
|
|
tr.innerHTML = '';
|
|
for (var i = 0; i < newHeader.length; i++)
|
|
{
|
|
var th = document.createElement('th');
|
|
this._setProps(th, newHeader[i]);
|
|
tr.appendChild(th);
|
|
}
|
|
this.header = newHeader;
|
|
// header change clears the whole grid by now
|
|
if (this.root)
|
|
this.root.setChildren([]);
|
|
}
|
|
|
|
TreeGrid.prototype.onExpand = function(node)
|
|
{
|
|
// handle node expand/collapse here
|
|
return true;
|
|
}
|
|
|
|
TreeGrid.prototype._setProps = function(el, props)
|
|
{
|
|
if (typeof props == 'string')
|
|
el.innerHTML = props;
|
|
else
|
|
for (var j in this.bindProps)
|
|
if (props[j])
|
|
el[j] = props[j];
|
|
}
|
|
|
|
function TreeGridNode(node, grid, level, insertBefore, startHidden)
|
|
{
|
|
this.grid = grid;
|
|
this.level = level;
|
|
if (this.level === undefined)
|
|
this.level = -1;
|
|
if (!grid.root)
|
|
grid.root = this;
|
|
else
|
|
{
|
|
this.leaf = node.leaf;
|
|
this.collapsed = node.collapsed || !node.leaf && (!node.children || !node.children.length);
|
|
this.cells = node.cells || [];
|
|
this.tr = document.createElement('tr');
|
|
if (startHidden)
|
|
this.tr.style.display = 'none';
|
|
this.tr._level = this.level;
|
|
for (var i = 0; i < this.grid.header.length; i++)
|
|
{
|
|
var td = document.createElement('td');
|
|
if (this.cells[i])
|
|
grid._setProps(td, this.cells[i]);
|
|
this.tr.appendChild(td);
|
|
}
|
|
var collapser = document.createElement('div');
|
|
if (this.leaf)
|
|
collapser.className = 'collapser collapser-inactive';
|
|
else
|
|
{
|
|
collapser.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
|
var self = this;
|
|
addListener(collapser, 'click', function(e) { self._toggleHandler(); });
|
|
}
|
|
var c0 = this.tr.cells[0];
|
|
c0.childNodes.length ? c0.insertBefore(collapser, c0.firstChild) : c0.appendChild(collapser);
|
|
if (grid._initialPadding === undefined)
|
|
{
|
|
var p0 = curStyle(c0).paddingLeft || '';
|
|
if (p0.substr(p0.length-2) == 'px')
|
|
p0 = parseInt(p0.substr(0, p0.length-2));
|
|
else
|
|
p0 = 0;
|
|
grid._initialPadding = p0;
|
|
}
|
|
c0.style.paddingLeft = (grid._initialPadding + grid.levelIndent * level) + 'px';
|
|
insertBefore ? grid.tbody.insertBefore(this.tr, insertBefore) : grid.tbody.appendChild(this.tr);
|
|
}
|
|
this.children = [];
|
|
if (node.children)
|
|
for (var i = 0; i < node.children.length; i++)
|
|
this.children.push(new TreeGridNode(node.children[i], grid, this.level+1, insertBefore, this.collapsed));
|
|
}
|
|
|
|
TreeGridNode.prototype._toggleHandler = function()
|
|
{
|
|
if (!this.grid.onExpand(this))
|
|
return;
|
|
this.toggle();
|
|
}
|
|
|
|
TreeGridNode.prototype.toggle = function()
|
|
{
|
|
if (this.leaf)
|
|
return;
|
|
this.collapsed = !this.collapsed;
|
|
this.tr.cells[0].firstChild.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
|
if (this.collapsed)
|
|
{
|
|
// collapse all children
|
|
var c = this.tr.nextSibling;
|
|
while (c && c._level > this.level)
|
|
{
|
|
c.style.display = 'none';
|
|
c = c.nextSibling;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// expand all children except children of collapsed subnodes
|
|
var st = this.children.concat([]);
|
|
while (st.length)
|
|
{
|
|
var e = st.pop();
|
|
e.tr.style.display = '';
|
|
if (!e.collapsed)
|
|
st = st.concat(e.children);
|
|
}
|
|
}
|
|
}
|
|
|
|
TreeGridNode.prototype.setChildren = function(isLeaf, newChildren)
|
|
{
|
|
if (!this.tr)
|
|
this.grid.tbody.innerHTML = '';
|
|
else
|
|
while (this.tr.nextSibling && this.tr.nextSibling._level > this.level)
|
|
this.grid.tbody.removeChild(this.tr.nextSibling);
|
|
this.leaf = isLeaf;
|
|
if (this.leaf)
|
|
this.tr.cells[0].firstChild.className = 'collapser collapser-inactive';
|
|
else
|
|
this.tr.cells[0].firstChild.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
|
this.children = [];
|
|
var insertBefore = this.tr && this.tr.nextSibling;
|
|
for (var i = 0; i < newChildren.length; i++)
|
|
this.children.push(new TreeGridNode(newChildren[i], this.grid, this.level+1, insertBefore, this.collapsed));
|
|
}
|