2016-02-20 13:31:29 +03:00
|
|
|
/**
|
2017-01-24 13:57:10 +03:00
|
|
|
* Very simple and fast tree grid/table, with support for fixed header and column, compatible with dynamic loading
|
|
|
|
*
|
2016-03-09 23:52:32 +03:00
|
|
|
* License: MPL 2.0+, (c) Vitaliy Filippov 2016+
|
2017-07-10 17:51:21 +03:00
|
|
|
* Version: 2017-04-24
|
2016-02-20 13:31:29 +03:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* USAGE:
|
|
|
|
*
|
2017-01-24 13:57:10 +03:00
|
|
|
* var TG = new TreeGrid({
|
|
|
|
* items: items,
|
|
|
|
* header: header,
|
|
|
|
* renderer: renderer,
|
|
|
|
* stickyHeaders: true|false,
|
|
|
|
* stickyColumnWidth: '25%'|'200px',
|
|
|
|
* stickyBorderWidth: 1
|
|
|
|
* });
|
2016-02-20 13:31:29 +03:00
|
|
|
* document.body.appendChild(TG.table);
|
|
|
|
*
|
2016-06-05 01:17:51 +03:00
|
|
|
* // renderer is a function that generates cell HTML properties for a node
|
2016-10-07 18:11:29 +03:00
|
|
|
* // for multi-row nodes renderer must return array of multiple rows
|
2016-06-05 01:17:51 +03:00
|
|
|
* renderer = function(node) { return [ { innerHTML, style, className, title }, ... ] }
|
|
|
|
*
|
2016-02-20 13:31:29 +03:00
|
|
|
* items: [ node={
|
2016-06-05 01:37:11 +03:00
|
|
|
* key: <unique key of this node across siblings>,
|
2016-02-20 13:31:29 +03:00
|
|
|
* children: [ node... ],
|
|
|
|
* leaf: true/false,
|
2016-02-24 17:08:00 +03:00
|
|
|
* collapsed: true/false,
|
2016-10-07 18:11:29 +03:00
|
|
|
* [ rows: <number of rows in this node> ],
|
2016-02-24 17:08:00 +03:00
|
|
|
* data: <user data>
|
2016-02-20 13:31:29 +03:00
|
|
|
* }, ... ]
|
|
|
|
*
|
|
|
|
* header: [ 'html' or { innerHTML, style, className, title }, ... ]
|
2016-03-10 15:04:09 +03:00
|
|
|
*
|
|
|
|
* Readonly properties:
|
|
|
|
*
|
|
|
|
* TG.table - grid <table> element
|
|
|
|
* TG.root - root node
|
|
|
|
*
|
|
|
|
* Methods:
|
|
|
|
*
|
|
|
|
* // change header and clear grid contents
|
|
|
|
* TG.setHeader(header);
|
|
|
|
*
|
|
|
|
* // change "leaf" status of a node and its children
|
|
|
|
* node.setChildren(isLeaf, newChildren);
|
|
|
|
*
|
|
|
|
* // expand/collapse a node
|
|
|
|
* node.toggle();
|
|
|
|
*
|
2016-06-05 01:17:51 +03:00
|
|
|
* // change node.data and re-render node
|
|
|
|
* node.render();
|
|
|
|
*
|
2016-03-10 15:04:09 +03:00
|
|
|
* // use simple cell editing
|
2016-03-10 15:59:57 +03:00
|
|
|
* TG.initCellEditing();
|
2016-03-10 15:04:09 +03:00
|
|
|
*
|
|
|
|
* // use simple mouse cell selection
|
2016-12-06 15:40:12 +03:00
|
|
|
* // boolean useMultiple: whether to initialise multi-cell selection (true) or single-cell (false)
|
|
|
|
* TG.initCellSelection(useMultiple);
|
2016-03-10 15:59:57 +03:00
|
|
|
*
|
|
|
|
* Callbacks:
|
|
|
|
*
|
|
|
|
* // called before expanding node. should return false if you want to prevent expanding the node
|
|
|
|
* TG.onExpand = function(node) {};
|
|
|
|
* // called before a cell is selected with mouse
|
2016-10-08 23:49:28 +03:00
|
|
|
* TG.onCellSelect = function(node, subrowIndex, colIndex, td) { return <whether to allow selection (boolean)>; }
|
2016-09-07 17:57:55 +03:00
|
|
|
* // called when all currently selected cells are deselected
|
2016-10-08 23:49:28 +03:00
|
|
|
* TG.onDeselectAllCells = function() {}
|
2016-03-10 15:59:57 +03:00
|
|
|
* // called before a cell is edited
|
2016-10-08 23:49:28 +03:00
|
|
|
* TG.onStartCellEdit = function(node, subrowIndex, colIndex, td) { return {
|
2016-03-10 15:59:57 +03:00
|
|
|
* abort: <whether to prevent editing>,
|
|
|
|
* value: <override initial textbox value>
|
|
|
|
* } }
|
|
|
|
* // called before a cell is stopped being edited
|
2016-10-08 23:49:28 +03:00
|
|
|
* TG.onStopCellEdit = function (node, subrowIndex, colIndex, value, td) { return { html: <new cell content> } }
|
2016-03-10 15:59:57 +03:00
|
|
|
*
|
2016-02-20 13:31:29 +03:00
|
|
|
*/
|
2016-06-05 01:17:51 +03:00
|
|
|
function TreeGrid(options)
|
2016-02-20 13:31:29 +03:00
|
|
|
{
|
2016-06-05 01:17:51 +03:00
|
|
|
this.renderer = options.renderer;
|
2016-02-20 13:31:29 +03:00
|
|
|
this.bindProps = { 'style': 1, 'className': 1, 'title': 1, 'innerHTML': 1 };
|
2016-10-07 18:11:29 +03:00
|
|
|
if (options.bind)
|
|
|
|
for (var i in options.bind)
|
|
|
|
this.bindProps[i] = 1;
|
2016-02-20 13:31:29 +03:00
|
|
|
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'));
|
2017-01-24 13:57:10 +03:00
|
|
|
var self = this;
|
|
|
|
if (options.stickyHeaders)
|
|
|
|
{
|
|
|
|
this.stickyHeaders = true;
|
|
|
|
this.toSync = [];
|
|
|
|
this.wrapper = document.createElement('div');
|
|
|
|
// table for fixed cell
|
|
|
|
this.fixedCell = document.createElement('table');
|
|
|
|
this.fixedCell.className = 'grid grid-fixed-cell';
|
|
|
|
this.fixedCell.appendChild(document.createElement('thead'));
|
|
|
|
this.fixedCell.firstChild.appendChild(document.createElement('tr'));
|
|
|
|
this.wrapper.appendChild(this.fixedCell);
|
|
|
|
// table for fixed row
|
|
|
|
this.fixedRow = document.createElement('table');
|
|
|
|
this.fixedRow.className = 'grid grid-fixed-row';
|
|
|
|
this.fixedRow.appendChild(this.thead);
|
|
|
|
this.table.insertBefore(document.createElement('thead'), this.tbody);
|
|
|
|
this.sizeRow = document.createElement('tr');
|
|
|
|
this.table.firstChild.appendChild(this.sizeRow);
|
|
|
|
this.fixedRowWrapper = document.createElement('div');
|
|
|
|
this.fixedRowWrapper.className = 'grid-fixed-row-wrapper';
|
|
|
|
this.fixedRowWrapper.appendChild(this.fixedRow);
|
|
|
|
this.wrapper.appendChild(this.fixedRowWrapper);
|
|
|
|
// table for fixed column
|
|
|
|
this.fixedCol = document.createElement('table');
|
|
|
|
this.fixedCol.className = 'grid grid-fixed-col';
|
|
|
|
this.fixedColBody = document.createElement('tbody');
|
|
|
|
this.fixedCol.appendChild(this.fixedColBody);
|
|
|
|
this.fixedColWrapper = document.createElement('div');
|
|
|
|
this.fixedColWrapper.className = 'grid-fixed-col-wrapper';
|
|
|
|
this.fixedColWrapper.appendChild(this.fixedCol);
|
|
|
|
this.fixedColWrapper2 = document.createElement('div');
|
|
|
|
this.fixedColWrapper2.className = 'grid-fixed-col-wrapper2';
|
|
|
|
this.fixedColWrapper2.appendChild(this.fixedColWrapper);
|
|
|
|
this.wrapper.appendChild(this.fixedColWrapper2);
|
|
|
|
// table body wrapper
|
|
|
|
this.wrapper.className = 'grid-wrapper';
|
|
|
|
this.tableWrapper = document.createElement('div');
|
|
|
|
this.tableWrapper.className = 'grid-body-wrapper';
|
|
|
|
this.tableWrapper.appendChild(this.table);
|
|
|
|
this.wrapper.appendChild(this.tableWrapper);
|
|
|
|
// sticky column width
|
|
|
|
this.stickyBorderWidth = options.stickyBorderWidth || 1;
|
|
|
|
if (options.stickyColumnWidth)
|
|
|
|
this.setStickyColumnWidth(options.stickyColumnWidth);
|
|
|
|
// scroll and resize handlers
|
|
|
|
if (!options.noStickyResizeHandler)
|
|
|
|
{
|
|
|
|
addListener(window, 'resize', function()
|
|
|
|
{
|
|
|
|
self.syncStickyHeaders(true);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
addListener(this.tableWrapper, 'scroll', function()
|
|
|
|
{
|
|
|
|
if (self.scrolling != 2)
|
|
|
|
{
|
|
|
|
self.fixedRowWrapper.scrollLeft = self.tableWrapper.scrollLeft;
|
|
|
|
self.fixedColWrapper.scrollTop = self.tableWrapper.scrollTop;
|
|
|
|
self.scrolling = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
self.scrolling = 0;
|
|
|
|
});
|
|
|
|
addListener(this.fixedColWrapper, 'scroll', function()
|
|
|
|
{
|
|
|
|
if (self.scrolling != 1)
|
|
|
|
{
|
|
|
|
self.tableWrapper.scrollTop = self.fixedColWrapper.scrollTop;
|
|
|
|
self.scrolling = 2;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
self.scrolling = 0;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
addListener(this.wrapper||this.table, 'mouseover', function(ev) { self.nodeOnHover(true, ev); });
|
|
|
|
addListener(this.wrapper||this.table, 'mouseout', function(ev) { self.nodeOnHover(false, ev); });
|
2016-06-05 01:17:51 +03:00
|
|
|
this.setHeader(options.header);
|
|
|
|
new TreeGridNode({ children: options.items }, this);
|
2016-02-20 13:31:29 +03:00
|
|
|
}
|
|
|
|
|
2017-01-24 13:57:10 +03:00
|
|
|
function TreeGridNode(node, grid, level, insertBeforeNode, startHidden, skipSync)
|
2016-12-06 15:40:12 +03:00
|
|
|
{
|
|
|
|
this.grid = grid;
|
|
|
|
this.level = level;
|
|
|
|
this.key = node.key;
|
|
|
|
this.data = node.data;
|
|
|
|
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);
|
2017-01-24 13:57:10 +03:00
|
|
|
this._oldCells = [];
|
2016-12-06 15:40:12 +03:00
|
|
|
this.tr = [];
|
|
|
|
for (var i = 0; i < (node.rows || 1); i++)
|
|
|
|
{
|
|
|
|
var tr = document.createElement('tr');
|
|
|
|
if (startHidden)
|
|
|
|
tr.style.display = 'none';
|
|
|
|
tr._node = this;
|
|
|
|
this.tr.push(tr);
|
|
|
|
}
|
2017-01-24 13:57:10 +03:00
|
|
|
if (grid.stickyHeaders)
|
|
|
|
{
|
|
|
|
this.col_tr = [];
|
|
|
|
for (var i = 0; i < (node.rows || 1); i++)
|
|
|
|
{
|
|
|
|
var tr = document.createElement('tr');
|
|
|
|
if (startHidden)
|
|
|
|
tr.style.display = 'none';
|
|
|
|
tr._node = this;
|
|
|
|
this.col_tr.push(tr);
|
|
|
|
}
|
|
|
|
}
|
2016-12-06 15:40:12 +03:00
|
|
|
this.render(undefined, undefined, false, true);
|
|
|
|
for (var i = 0; i < this.tr.length; i++)
|
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
insertBeforeNode ? grid.tbody.insertBefore(this.tr[i], insertBeforeNode.tr[0]) : grid.tbody.appendChild(this.tr[i]);
|
|
|
|
}
|
|
|
|
if (grid.stickyHeaders)
|
|
|
|
{
|
|
|
|
for (var i = 0; i < this.col_tr.length; i++)
|
|
|
|
{
|
|
|
|
insertBeforeNode ? grid.fixedColBody.insertBefore(this.col_tr[i], insertBeforeNode.col_tr[0]) : grid.fixedColBody.appendChild(this.col_tr[i]);
|
|
|
|
}
|
|
|
|
grid.toSync.push(this);
|
2016-12-06 15:40:12 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
this.children = [];
|
|
|
|
this.childrenByKey = {};
|
|
|
|
if (node.children)
|
|
|
|
{
|
|
|
|
for (var i = 0; i < node.children.length; i++)
|
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
var child = new TreeGridNode(node.children[i], grid, this.level+1, insertBeforeNode, this.collapsed, true);
|
2016-12-06 15:40:12 +03:00
|
|
|
child._index = i;
|
|
|
|
this.children.push(child);
|
|
|
|
if (child.key !== undefined)
|
|
|
|
this.childrenByKey[child.key] = child;
|
|
|
|
}
|
|
|
|
}
|
2017-01-24 13:57:10 +03:00
|
|
|
if (!skipSync)
|
|
|
|
this.grid.syncStickyHeaders();
|
2016-12-06 15:40:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
2017-01-24 13:57:10 +03:00
|
|
|
function htmlspecialchars(text)
|
2016-08-12 15:29:58 +03:00
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
return (''+text).replace(/&/g, '&')
|
|
|
|
.replace(/'/g, ''') // '
|
|
|
|
.replace(/"/g, '"') // "
|
|
|
|
.replace(/</g, '<')
|
|
|
|
.replace(/>/g, '>');
|
|
|
|
}
|
|
|
|
|
|
|
|
TreeGrid.prototype.nodeOnHover = function(hover, ev)
|
|
|
|
{
|
|
|
|
ev = ev||window.event;
|
|
|
|
var e = ev.target||ev.srcElement;
|
|
|
|
while (e.nodeName != 'TR' && e && e != this.wrapper && e != this.table)
|
|
|
|
e = e.parentNode;
|
|
|
|
if (!e._node)
|
|
|
|
return;
|
|
|
|
var subrow = 0;
|
|
|
|
while (e.previousSibling && e.previousSibling._node == e._node)
|
|
|
|
{
|
|
|
|
e = e.previousSibling;
|
|
|
|
subrow++;
|
|
|
|
}
|
|
|
|
if (hover)
|
|
|
|
e._node.tr[subrow].className += ' hover';
|
|
|
|
else
|
|
|
|
e._node.tr[subrow].className = e._node.tr[subrow].className.replace(/ hover/g, '');
|
|
|
|
if (e._node.col_tr)
|
|
|
|
{
|
|
|
|
if (hover)
|
|
|
|
e._node.col_tr[subrow].className += ' hover';
|
|
|
|
else
|
|
|
|
e._node.col_tr[subrow].className = e._node.col_tr[subrow].className.replace(/ hover/g, '');
|
|
|
|
}
|
2016-08-15 16:22:13 +03:00
|
|
|
}
|
|
|
|
|
2017-01-24 13:57:10 +03:00
|
|
|
TreeGrid.prototype.setStickyColumnWidth = function(w)
|
2016-12-06 15:40:12 +03:00
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
this.fixedColWrapper2.style.width = w;
|
|
|
|
if (!this.fixedColWrapper2.offsetWidth)
|
|
|
|
return;
|
|
|
|
w = this.fixedColWrapper2.offsetWidth;
|
|
|
|
this.fixedColWrapper.style.width = (w+this.fixedColWrapper.offsetWidth-this.fixedColWrapper.clientWidth)+'px';
|
2017-07-10 17:51:21 +03:00
|
|
|
this.fixedCol.style.width = this.fixedColWrapper.clientWidth+'px';
|
2017-01-24 13:57:10 +03:00
|
|
|
this.tableWrapper.style.paddingLeft = (w-this.stickyBorderWidth)+'px';
|
|
|
|
this.fixedRowWrapper.style.paddingLeft = (w-this.stickyBorderWidth)+'px';
|
|
|
|
this.fixedCell.style.width = w+'px';
|
2016-12-06 15:40:12 +03:00
|
|
|
}
|
|
|
|
|
2017-01-24 13:57:10 +03:00
|
|
|
TreeGrid.prototype.syncStickyHeaders = function(sync_all)
|
2016-08-15 16:22:13 +03:00
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
if (!this.stickyHeaders || !this.wrapper.offsetParent)
|
|
|
|
return;
|
|
|
|
if (this.fixedCol.offsetWidth)
|
2017-07-10 17:51:21 +03:00
|
|
|
this.setStickyColumnWidth(this.fixedCol.getBoundingClientRect().width+'px');
|
2017-01-24 13:57:10 +03:00
|
|
|
var h1, h2, h = [];
|
|
|
|
if (sync_all)
|
2016-08-13 23:13:37 +03:00
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
for (var i = 0; i < this.table.rows.length-1; i++)
|
2016-08-15 16:22:13 +03:00
|
|
|
{
|
2017-07-10 17:51:21 +03:00
|
|
|
h1 = this.table.rows[i+1].getBoundingClientRect().height;
|
|
|
|
h2 = this.fixedCol.rows[i].getBoundingClientRect().height;
|
2017-01-24 13:57:10 +03:00
|
|
|
if (h1 && h2)
|
|
|
|
h[i] = h1 < h2 ? h2 : h1;
|
|
|
|
}
|
|
|
|
for (var i = 0; i < this.table.rows.length-1; i++)
|
|
|
|
{
|
|
|
|
if (h[i])
|
|
|
|
{
|
|
|
|
this.table.rows[i+1].style.height = h[i]+'px';
|
|
|
|
this.fixedCol.rows[i].style.height = h[i]+'px';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (var i = 0; i < this.toSync.length; i++)
|
|
|
|
{
|
|
|
|
for (var k = 0; k < this.toSync[i].tr.length; k++)
|
|
|
|
{
|
2017-07-10 17:51:21 +03:00
|
|
|
h1 = this.toSync[i].tr[k].getBoundingClientRect().height;
|
|
|
|
h2 = this.toSync[i].col_tr[k].getBoundingClientRect().height;
|
2017-01-24 13:57:10 +03:00
|
|
|
h.push(h1 && h2 && (h1 < h2 ? h2 : h1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (var i = 0, j = 0; i < this.toSync.length; i++)
|
|
|
|
{
|
|
|
|
for (var k = 0; k < this.toSync[i].tr.length; k++, j++)
|
|
|
|
{
|
|
|
|
if (h[j])
|
|
|
|
{
|
|
|
|
this.toSync[i].tr[k].style.height = h[j]+'px';
|
|
|
|
this.toSync[i].col_tr[k].style.height = h[j]+'px';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-13 23:13:37 +03:00
|
|
|
}
|
2017-01-24 13:57:10 +03:00
|
|
|
this.toSync = [];
|
|
|
|
var trh = this.fixedRow.rows[0], w = [];
|
|
|
|
for (var i = 0; i < trh.cells.length; i++)
|
2017-07-10 17:51:21 +03:00
|
|
|
w[i] = this.sizeRow.cells[i] && this.sizeRow.cells[i].getBoundingClientRect().width || 0;
|
|
|
|
this.fixedRow.style.width = this.table.getBoundingClientRect().width+'px';
|
2017-01-24 13:57:10 +03:00
|
|
|
for (var i = 0; i < trh.cells.length; i++)
|
|
|
|
trh.cells[i].style.width = w[i]+'px';
|
|
|
|
this.tableWrapper.style.paddingTop = (this.fixedRow.offsetHeight-this.sizeRow.offsetHeight)+'px';
|
|
|
|
this.fixedColWrapper2.style.bottom = (this.tableWrapper.offsetHeight-this.tableWrapper.clientHeight)+'px';
|
|
|
|
this.fixedRowWrapper.style.right = (this.tableWrapper.offsetWidth-this.tableWrapper.clientWidth)+'px';
|
2017-07-10 17:51:21 +03:00
|
|
|
this.fixedColWrapper.style.paddingTop = this.fixedRow.getBoundingClientRect().height+'px';
|
2017-01-24 15:19:58 +03:00
|
|
|
if (this.fixedCell.rows[0].cells[0])
|
2017-07-10 17:51:21 +03:00
|
|
|
this.fixedCell.rows[0].cells[0].style.height = (this.fixedRow.getBoundingClientRect().height)+'px';
|
2016-08-12 15:29:58 +03:00
|
|
|
}
|
|
|
|
|
2016-02-20 13:31:29 +03:00
|
|
|
TreeGrid.prototype.setHeader = function(newHeader)
|
|
|
|
{
|
|
|
|
var tr = this.thead.rows[0];
|
|
|
|
tr.innerHTML = '';
|
2017-01-24 13:57:10 +03:00
|
|
|
if (this.stickyHeaders)
|
|
|
|
{
|
|
|
|
this.sizeRow.innerHTML = '';
|
|
|
|
this.fixedCell.rows[0].innerHTML = '';
|
|
|
|
}
|
2016-02-20 13:31:29 +03:00
|
|
|
for (var i = 0; i < newHeader.length; i++)
|
|
|
|
{
|
|
|
|
var th = document.createElement('th');
|
|
|
|
this._setProps(th, newHeader[i]);
|
2017-01-24 13:57:10 +03:00
|
|
|
if (this.stickyHeaders && i == 0)
|
|
|
|
this.fixedCell.rows[0].appendChild(th);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tr.appendChild(th);
|
|
|
|
if (this.stickyHeaders)
|
|
|
|
this.sizeRow.appendChild(th.cloneNode(true));
|
|
|
|
}
|
2016-02-20 13:31:29 +03:00
|
|
|
}
|
|
|
|
this.header = newHeader;
|
2016-03-10 13:23:16 +03:00
|
|
|
this.editedCells = [];
|
2016-02-20 13:31:29 +03:00
|
|
|
// header change clears the whole grid by now
|
|
|
|
if (this.root)
|
2016-03-04 12:27:56 +03:00
|
|
|
this.root.setChildren(false, []);
|
2016-02-20 13:31:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
TreeGrid.prototype.onExpand = function(node)
|
|
|
|
{
|
|
|
|
// handle node expand/collapse here
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
TreeGrid.prototype._setProps = function(el, props)
|
|
|
|
{
|
2016-05-18 18:38:22 +03:00
|
|
|
if (props === undefined || props === null)
|
2016-12-06 15:40:12 +03:00
|
|
|
{
|
2016-05-18 18:38:22 +03:00
|
|
|
for (var j in this.bindProps)
|
2016-12-06 15:40:12 +03:00
|
|
|
{
|
|
|
|
if (j == 'innerHTML' || j == 'className')
|
|
|
|
el[j] = '';
|
|
|
|
else
|
|
|
|
el.setAttribute(j, '');
|
|
|
|
}
|
|
|
|
}
|
2016-05-18 18:38:22 +03:00
|
|
|
else if (typeof props == 'string')
|
2016-12-06 15:40:12 +03:00
|
|
|
{
|
2016-02-20 13:31:29 +03:00
|
|
|
el.innerHTML = props;
|
2016-12-06 15:40:12 +03:00
|
|
|
}
|
2016-02-20 13:31:29 +03:00
|
|
|
else
|
2016-12-06 15:40:12 +03:00
|
|
|
{
|
2016-02-20 13:31:29 +03:00
|
|
|
for (var j in this.bindProps)
|
2016-12-06 15:40:12 +03:00
|
|
|
{
|
|
|
|
if (j == 'innerHTML' || j == 'className')
|
|
|
|
el[j] = (props[j] !== undefined ? props[j] : '');
|
|
|
|
else
|
|
|
|
el.setAttribute(j, (props[j] !== undefined ? props[j] : ''));
|
|
|
|
}
|
|
|
|
}
|
2016-02-20 13:31:29 +03:00
|
|
|
}
|
|
|
|
|
2016-03-09 23:52:32 +03:00
|
|
|
// Simple cell editing
|
|
|
|
|
2016-08-18 17:33:36 +03:00
|
|
|
TreeGrid.prototype._getCellIndex = function(n, sticky)
|
2016-03-09 23:52:32 +03:00
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
var i = 0, p = n;
|
|
|
|
while ((p = p.previousSibling))
|
|
|
|
i += (p.nodeType != 3 ? 1 : 0);
|
|
|
|
if (sticky && this.stickyHeaders)
|
|
|
|
{
|
|
|
|
p = n.parentNode.parentNode.parentNode;
|
|
|
|
if (p == this.table || p == this.fixedRow)
|
|
|
|
i++;
|
|
|
|
}
|
2016-03-09 23:52:32 +03:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2016-10-08 23:49:28 +03:00
|
|
|
TreeGrid.prototype._getSubrow = function(cell)
|
|
|
|
{
|
|
|
|
var row = cell.parentNode, subrow = 0;
|
|
|
|
while (row.previousSibling && row.previousSibling._node == row._node)
|
|
|
|
{
|
|
|
|
row = row.previousSibling;
|
|
|
|
subrow++;
|
|
|
|
}
|
|
|
|
return subrow;
|
|
|
|
}
|
|
|
|
|
2016-03-10 15:59:57 +03:00
|
|
|
TreeGrid.prototype.initCellEditing = function()
|
2016-03-09 23:52:32 +03:00
|
|
|
{
|
|
|
|
var self = this;
|
2016-03-10 13:23:16 +03:00
|
|
|
self.editedCells = []; // FIXME maybe remove and use just class selector
|
2016-03-09 23:52:32 +03:00
|
|
|
addListener(this.table, 'dblclick', function(evt)
|
|
|
|
{
|
2016-03-10 14:26:55 +03:00
|
|
|
evt = evt||window.event;
|
2016-03-09 23:52:32 +03:00
|
|
|
var td = evt.target||evt.srcElement;
|
|
|
|
while (td.nodeName != 'TABLE' && td.nodeName != 'TD')
|
|
|
|
td = td.parentNode;
|
2017-01-24 13:57:10 +03:00
|
|
|
if (td.nodeName == 'TD' && td.parentNode._node && td.className.indexOf('celleditor') < 0)
|
2016-03-09 23:52:32 +03:00
|
|
|
{
|
2016-10-08 23:49:28 +03:00
|
|
|
var params = self.onStartCellEdit &&
|
|
|
|
self.onStartCellEdit(td.parentNode._node, self._getSubrow(td), self._getCellIndex(td, true), td) || {};
|
2016-03-09 23:52:32 +03:00
|
|
|
if (params.abort)
|
|
|
|
return;
|
2017-07-10 17:51:21 +03:00
|
|
|
self.startCellEditing(td, params);
|
2016-03-09 23:52:32 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
addListener(document.body, 'click', function(evt)
|
|
|
|
{
|
|
|
|
if (!self.editedCells.length)
|
|
|
|
return;
|
|
|
|
var td = evt.target||evt.srcElement;
|
|
|
|
while (td && td.nodeName != 'TD')
|
|
|
|
td = td.parentNode;
|
|
|
|
if (td && /\bcelleditor\b/.exec(td.className))
|
|
|
|
return;
|
|
|
|
for (var i = self.editedCells.length-1; i >= 0; i--)
|
|
|
|
self.stopCellEditing(self.editedCells[i], 1);
|
|
|
|
self.editedCells = [];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-07-10 17:51:21 +03:00
|
|
|
TreeGrid.prototype.startCellEditing = function(td, params)
|
|
|
|
{
|
|
|
|
var self = this;
|
|
|
|
self.editedCells.push(td);
|
|
|
|
if (params.value === undefined)
|
|
|
|
params.value = td.innerHTML;
|
|
|
|
td._origWidth = td.style.width;
|
|
|
|
td._origMinWidth = td.style.minWidth;
|
|
|
|
td.style.width = td.style.minWidth = td.getBoundingClientRect().width+'px';
|
|
|
|
td.className += ' celleditor';
|
|
|
|
if ('ActiveXObject' in window)
|
|
|
|
{
|
|
|
|
td.innerHTML = '<div><input type="text" value="'+htmlspecialchars(params.value)+'" /></div>';
|
|
|
|
td.style.minHeight = td.offsetHeight+'px';
|
|
|
|
td.style.height = 'inherit';
|
|
|
|
td.parentNode.style.height = '1px';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
td.innerHTML = '<input type="text" value="'+htmlspecialchars(params.value)+'" />';
|
|
|
|
var inp = td.getElementsByTagName('input')[0];
|
|
|
|
addListener(inp, 'keydown', function(evt)
|
|
|
|
{
|
|
|
|
if (evt.keyCode == 13 || evt.keyCode == 10)
|
|
|
|
self.stopCellEditing(td);
|
|
|
|
});
|
|
|
|
inp.focus();
|
|
|
|
}
|
|
|
|
|
2016-03-09 23:52:32 +03:00
|
|
|
TreeGrid.prototype.stopCellEditing = function(td, _int)
|
|
|
|
{
|
2016-08-18 17:33:36 +03:00
|
|
|
var i = this._getCellIndex(td, true);
|
2016-10-08 23:49:28 +03:00
|
|
|
var subrow = this._getSubrow(td);
|
2016-06-05 01:17:51 +03:00
|
|
|
var node = td.parentNode._node;
|
|
|
|
node._oldCells[i] = undefined;
|
2016-12-06 15:40:12 +03:00
|
|
|
td.style.width = td._origWidth;
|
2017-01-24 15:19:58 +03:00
|
|
|
td.style.minWidth = td._origMinWidth;
|
2016-12-06 15:40:12 +03:00
|
|
|
var inp = td.getElementsByTagName('input')[0];
|
2016-10-08 23:49:28 +03:00
|
|
|
var params = this.onStopCellEdit &&
|
2016-12-06 15:40:12 +03:00
|
|
|
this.onStopCellEdit(node, subrow, i, inp ? inp.value : null, td) || {};
|
2016-10-08 23:49:28 +03:00
|
|
|
node.render(i, subrow, true);
|
2016-03-09 23:52:32 +03:00
|
|
|
if (!_int)
|
|
|
|
{
|
|
|
|
for (var i = 0; i < this.editedCells.length; i++)
|
|
|
|
{
|
|
|
|
if (this.editedCells[i] == td)
|
|
|
|
{
|
|
|
|
this.editedCells.splice(i, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-10 14:46:50 +03:00
|
|
|
// Simple cell selection
|
|
|
|
|
2016-12-06 15:40:12 +03:00
|
|
|
TreeGrid.prototype.initCellSelection = function(useMultiple)
|
2016-03-10 14:46:50 +03:00
|
|
|
{
|
|
|
|
var self = this;
|
2016-03-10 15:59:57 +03:00
|
|
|
|
2016-04-13 15:51:34 +03:00
|
|
|
self.selectCell = function(cell)
|
|
|
|
{
|
|
|
|
if (!cell.parentNode._node || cell.className.indexOf(' selected') >= 0)
|
|
|
|
return;
|
2016-08-18 17:33:36 +03:00
|
|
|
var i = self._getCellIndex(cell, true);
|
2016-10-08 23:49:28 +03:00
|
|
|
var subrow = self._getSubrow(cell);
|
|
|
|
if (!self.onCellSelect || self.onCellSelect(cell.parentNode._node, subrow, i, cell))
|
2016-04-13 15:51:34 +03:00
|
|
|
cell.className += ' selected';
|
|
|
|
};
|
|
|
|
|
2016-09-07 17:57:55 +03:00
|
|
|
self.deselectAll = function(cell)
|
2016-04-13 15:51:34 +03:00
|
|
|
{
|
2017-07-10 17:51:21 +03:00
|
|
|
var els = (self.wrapper||self.table).querySelectorAll('td.selected');
|
2016-09-07 17:57:55 +03:00
|
|
|
var deselected = [];
|
|
|
|
if (self.onDeselectAllCells)
|
|
|
|
self.onDeselectAllCells();
|
|
|
|
for (var i = 0; i < els.length; i++)
|
|
|
|
els[i].className = els[i].className.replace(' selected', '');
|
2016-04-13 15:51:34 +03:00
|
|
|
};
|
|
|
|
|
2016-12-06 15:40:12 +03:00
|
|
|
if (useMultiple)
|
|
|
|
initMultiCellSelection(self);
|
|
|
|
else
|
|
|
|
initSingleCellSelection(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
function initSingleCellSelection(self)
|
|
|
|
{
|
2017-07-10 17:51:21 +03:00
|
|
|
addListener(self.wrapper||self.table, 'mousedown', function(evt)
|
2016-12-06 15:40:12 +03:00
|
|
|
{
|
|
|
|
self.deselectAll();
|
|
|
|
if (evt.which != 1)
|
|
|
|
return;
|
|
|
|
var td = evt.target||evt.srcElement;
|
|
|
|
while (td && (td.nodeName != 'TD' && td.nodeName != 'TH'))
|
|
|
|
td = td.parentNode;
|
|
|
|
if (td)
|
|
|
|
self.selectCell(td);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function initMultiCellSelection(self)
|
|
|
|
{
|
|
|
|
var startDrag, dragDiv;
|
|
|
|
|
|
|
|
self.table.className += ' disable-text-select';
|
|
|
|
|
2017-01-24 13:57:10 +03:00
|
|
|
addListener(self.wrapper||self.table, 'mousedown', function(evt)
|
2016-03-10 14:46:50 +03:00
|
|
|
{
|
2016-03-10 15:59:57 +03:00
|
|
|
evt = getEventCoord(evt);
|
|
|
|
if (!evt.shiftKey)
|
2016-09-07 17:57:55 +03:00
|
|
|
self.deselectAll();
|
2016-03-10 14:46:50 +03:00
|
|
|
if (evt.which != 1)
|
|
|
|
return;
|
|
|
|
startDrag = [ evt.pageX, evt.pageY ];
|
|
|
|
if (!dragDiv)
|
|
|
|
{
|
|
|
|
dragDiv = document.createElement('div');
|
|
|
|
dragDiv.className = 'selection-rect';
|
2016-04-04 14:14:48 +03:00
|
|
|
dragDiv.style.display = 'none';
|
2016-03-10 14:46:50 +03:00
|
|
|
document.body.appendChild(dragDiv);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
addListener(document.body, 'mousemove', function(evt)
|
|
|
|
{
|
|
|
|
if (startDrag)
|
|
|
|
{
|
|
|
|
evt = getEventCoord(evt);
|
2016-04-04 14:14:48 +03:00
|
|
|
if (dragDiv.style.display == 'none')
|
|
|
|
{
|
|
|
|
if ((startDrag[0] < evt.pageX-10 || startDrag[0] > evt.pageX+10) &&
|
|
|
|
(startDrag[1] < evt.pageY-10 || startDrag[1] > evt.pageY+10))
|
|
|
|
dragDiv.style.display = 'block';
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
}
|
2016-03-10 14:46:50 +03:00
|
|
|
if (startDrag[0] < evt.pageX)
|
|
|
|
{
|
|
|
|
dragDiv.style.left = startDrag[0]+'px';
|
|
|
|
dragDiv.style.width = (evt.pageX-startDrag[0])+'px';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dragDiv.style.left = evt.pageX+'px';
|
|
|
|
dragDiv.style.width = (startDrag[0]-evt.pageX)+'px';
|
|
|
|
}
|
|
|
|
if (startDrag[1] < evt.pageY)
|
|
|
|
{
|
|
|
|
dragDiv.style.top = startDrag[1]+'px';
|
|
|
|
dragDiv.style.height = (evt.pageY-startDrag[1])+'px';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dragDiv.style.top = evt.pageY+'px';
|
|
|
|
dragDiv.style.height = (startDrag[1]-evt.pageY)+'px';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
addListener(document.body, 'mouseup', function(evt)
|
|
|
|
{
|
|
|
|
if (startDrag)
|
|
|
|
{
|
|
|
|
evt = getEventCoord(evt);
|
|
|
|
dragDiv.style.display = 'none';
|
|
|
|
var x1 = startDrag[0], y1 = startDrag[1], x2 = evt.pageX, y2 = evt.pageY;
|
|
|
|
if (x2 < x1)
|
|
|
|
{
|
|
|
|
var t = x2;
|
|
|
|
x2 = x1;
|
|
|
|
x1 = t;
|
|
|
|
}
|
|
|
|
if (y2 < y1)
|
|
|
|
{
|
|
|
|
var t = y2;
|
|
|
|
y2 = y1;
|
|
|
|
y1 = t;
|
|
|
|
}
|
|
|
|
startDrag = null;
|
|
|
|
if (x2 > x1+10 && y2 > y1+10)
|
2016-06-05 01:17:51 +03:00
|
|
|
{
|
2016-12-06 15:40:12 +03:00
|
|
|
var bodyPos = self.tbody.getBoundingClientRect();
|
2016-06-05 01:17:51 +03:00
|
|
|
if (x1 < bodyPos.left+self.tbody.offsetWidth &&
|
|
|
|
y1 < bodyPos.top+self.tbody.offsetHeight &&
|
|
|
|
x2 > bodyPos.left && y2 > bodyPos.top)
|
|
|
|
{
|
|
|
|
if (x1 <= bodyPos.left)
|
|
|
|
x1 = bodyPos.left+1;
|
|
|
|
if (y1 <= bodyPos.top)
|
|
|
|
y1 = bodyPos.top+1;
|
|
|
|
if (x2 >= bodyPos.left+self.tbody.offsetWidth)
|
|
|
|
x2 = bodyPos.left+self.tbody.offsetWidth-1;
|
|
|
|
if (y2 >= bodyPos.top+self.tbody.offsetHeight)
|
|
|
|
y2 = bodyPos.top+self.tbody.offsetHeight-1;
|
|
|
|
var e1 = document.elementFromPoint(x1, y1);
|
|
|
|
var e2 = document.elementFromPoint(x2, y2);
|
|
|
|
while (e1 && e1.nodeName != 'TD')
|
|
|
|
e1 = e1.parentNode;
|
|
|
|
while (e2 && e2.nodeName != 'TD')
|
|
|
|
e2 = e2.parentNode;
|
|
|
|
if (e1 && e2)
|
|
|
|
{
|
|
|
|
var col1 = self._getCellIndex(e1);
|
|
|
|
var col2 = self._getCellIndex(e2);
|
|
|
|
var row = e1.parentNode;
|
|
|
|
do
|
|
|
|
{
|
2016-08-12 17:08:01 +03:00
|
|
|
if (row.style.display != 'none' && row.style.visibility != 'hidden')
|
2016-06-05 01:17:51 +03:00
|
|
|
for (var i = col1; i <= col2; i++)
|
2016-08-12 17:08:01 +03:00
|
|
|
if (!self.stickyInit || i != 1)
|
|
|
|
self.selectCell(row.cells[i]);
|
2016-06-05 01:17:51 +03:00
|
|
|
if (row == e2.parentNode)
|
|
|
|
break;
|
|
|
|
row = row.nextSibling;
|
|
|
|
} while (row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-04 14:14:48 +03:00
|
|
|
else
|
|
|
|
{
|
|
|
|
var t = evt.target || evt.srcElement;
|
2016-04-13 15:51:34 +03:00
|
|
|
self.selectCell(t);
|
2016-04-04 14:14:48 +03:00
|
|
|
}
|
|
|
|
if (x2 > x1+10 && y2 > y1+10 || evt.shiftKey)
|
2016-03-10 14:46:50 +03:00
|
|
|
{
|
2016-03-10 15:59:57 +03:00
|
|
|
if (document.selection)
|
|
|
|
document.selection.empty();
|
|
|
|
else
|
|
|
|
window.getSelection().removeAllRanges();
|
2016-03-10 14:46:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-03-09 23:52:32 +03:00
|
|
|
// Tree grid node class
|
|
|
|
|
2016-06-06 14:02:11 +03:00
|
|
|
TreeGridNode.prototype.getKey = function()
|
|
|
|
{
|
|
|
|
return this.key;
|
|
|
|
}
|
|
|
|
|
2016-06-05 01:37:11 +03:00
|
|
|
TreeGridNode.prototype.getChildByKey = function(key)
|
|
|
|
{
|
|
|
|
return this.childrenByKey[key];
|
2016-02-20 13:31:29 +03:00
|
|
|
}
|
|
|
|
|
2016-06-05 01:17:51 +03:00
|
|
|
TreeGridNode.prototype._addCollapser = function()
|
2016-05-18 18:38:22 +03:00
|
|
|
{
|
|
|
|
var collapser = document.createElement('div');
|
|
|
|
if (this.leaf)
|
|
|
|
collapser.className = 'collapser collapser-inactive';
|
|
|
|
else
|
|
|
|
{
|
|
|
|
collapser.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
|
|
|
addListener(collapser, 'click', this._getToggleHandler());
|
|
|
|
}
|
2017-01-24 13:57:10 +03:00
|
|
|
var c0 = (this.col_tr || this.tr)[0].cells[0];
|
2016-05-18 18:38:22 +03:00
|
|
|
c0.childNodes.length ? c0.insertBefore(collapser, c0.firstChild) : c0.appendChild(collapser);
|
|
|
|
if (this.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;
|
|
|
|
this.grid._initialPadding = p0;
|
|
|
|
}
|
|
|
|
c0.style.paddingLeft = (this.grid._initialPadding + this.grid.levelIndent * this.level + 20) + 'px';
|
|
|
|
}
|
|
|
|
|
2016-06-05 01:17:51 +03:00
|
|
|
TreeGridNode.prototype._hashEqual = function(a, b)
|
|
|
|
{
|
|
|
|
for (var i in a)
|
|
|
|
if (b[i] !== a[i])
|
|
|
|
return false;
|
|
|
|
for (var i in b)
|
|
|
|
if (b[i] !== a[i])
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-10-08 23:49:28 +03:00
|
|
|
TreeGridNode.prototype._renderCell = function(rowIndex, colIndex, cell, force)
|
2016-06-05 01:17:51 +03:00
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
var tr = (this.grid.stickyHeaders && colIndex == 0 ? this.col_tr : this.tr)[rowIndex];
|
|
|
|
var ri = (this.grid.stickyHeaders && colIndex > 0 ? colIndex-1 : 0);
|
2016-10-07 18:11:29 +03:00
|
|
|
while (!tr.cells[ri])
|
|
|
|
tr.appendChild(document.createElement('td'));
|
2016-06-05 01:17:51 +03:00
|
|
|
// virtualDOM-like approach: compare old HTML properties
|
2016-10-08 23:49:28 +03:00
|
|
|
if (force || !this._oldCells[rowIndex] || !this._oldCells[rowIndex][colIndex] ||
|
2017-07-10 17:51:21 +03:00
|
|
|
!this._hashEqual(this._oldCells[rowIndex][colIndex], cell||{}))
|
2016-06-05 01:17:51 +03:00
|
|
|
{
|
2016-12-06 15:40:12 +03:00
|
|
|
var old;
|
2017-01-24 13:57:10 +03:00
|
|
|
if (colIndex == 0 && rowIndex == 0)
|
2016-12-06 15:40:12 +03:00
|
|
|
{
|
|
|
|
old = {
|
|
|
|
width: tr.cells[ri].style.width,
|
|
|
|
height: tr.cells[ri].style.height,
|
|
|
|
position: tr.cells[ri].style.position,
|
|
|
|
display: tr.cells[ri].style.display,
|
|
|
|
zIndex: tr.cells[ri].style.zIndex
|
|
|
|
};
|
|
|
|
}
|
2016-10-07 18:11:29 +03:00
|
|
|
this.grid._setProps(tr.cells[ri], cell);
|
2016-10-08 23:49:28 +03:00
|
|
|
if (colIndex == 0)
|
|
|
|
{
|
|
|
|
if (rowIndex == 0)
|
|
|
|
this._addCollapser();
|
|
|
|
else
|
|
|
|
tr.cells[ri].style.paddingLeft = (this.grid._initialPadding + this.grid.levelIndent * this.level + 20) + 'px';
|
|
|
|
}
|
2016-12-06 15:40:12 +03:00
|
|
|
return true;
|
2016-06-05 01:17:51 +03:00
|
|
|
}
|
2016-12-06 15:40:12 +03:00
|
|
|
return false;
|
2016-06-05 01:17:51 +03:00
|
|
|
}
|
|
|
|
|
2017-01-24 13:57:10 +03:00
|
|
|
TreeGridNode.prototype.render = function(colidx, rowidx, force, skipSync)
|
2016-05-18 18:38:22 +03:00
|
|
|
{
|
2016-10-07 18:11:29 +03:00
|
|
|
var cells = this.grid.renderer(this, colidx, rowidx);
|
|
|
|
if (this.tr.length == 1)
|
|
|
|
cells = [ cells ];
|
2016-10-08 23:49:28 +03:00
|
|
|
if (colidx === null || colidx < 0 || colidx >= this.grid.header.length)
|
2016-10-07 18:11:29 +03:00
|
|
|
colidx = undefined;
|
2016-10-08 23:49:28 +03:00
|
|
|
if (rowidx === null || rowidx < 0 || rowidx >= this.tr.length)
|
2016-10-07 18:11:29 +03:00
|
|
|
rowidx = undefined;
|
2016-12-06 15:40:12 +03:00
|
|
|
var modified = false;
|
2016-10-07 18:11:29 +03:00
|
|
|
if (rowidx !== undefined)
|
2016-06-05 01:17:51 +03:00
|
|
|
{
|
2016-10-07 18:11:29 +03:00
|
|
|
if (colidx !== undefined)
|
|
|
|
{
|
2016-12-06 15:40:12 +03:00
|
|
|
modified = this._renderCell(rowidx, colidx, cells[rowidx] && cells[rowidx][colidx], force) || modified;
|
|
|
|
this._oldCells[rowidx] = this._oldCells[rowidx] || [];
|
2016-10-07 18:11:29 +03:00
|
|
|
this._oldCells[rowidx][colidx] = cells[rowidx] && cells[rowidx][colidx];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (var j = 0; j < this.grid.header.length; j++)
|
2016-12-06 15:40:12 +03:00
|
|
|
modified = this._renderCell(rowidx, j, cells[rowidx] && cells[rowidx][j], force) || modified;
|
2016-10-07 18:11:29 +03:00
|
|
|
this._oldCells[rowidx] = cells[rowidx];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (colidx !== undefined)
|
|
|
|
{
|
|
|
|
for (var i = 0; i < this.tr.length; i++)
|
|
|
|
{
|
2016-12-06 15:40:12 +03:00
|
|
|
modified = this._renderCell(i, colidx, cells[i] && cells[i][colidx], force) || modified;
|
|
|
|
this._oldCells[i] = this._oldCells[i] || [];
|
2016-10-07 18:11:29 +03:00
|
|
|
this._oldCells[i][colidx] = cells[i] && cells[i][colidx];
|
|
|
|
}
|
2016-06-05 01:17:51 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-10-07 18:11:29 +03:00
|
|
|
for (var i = 0; i < this.tr.length; i++)
|
|
|
|
for (var j = 0; j < this.grid.header.length; j++)
|
2016-12-06 15:40:12 +03:00
|
|
|
modified = this._renderCell(i, j, cells[i] && cells[i][j], force) || modified;
|
2016-06-05 01:17:51 +03:00
|
|
|
this._oldCells = cells;
|
|
|
|
}
|
2017-01-24 13:57:10 +03:00
|
|
|
if (modified && this.grid.stickyHeaders)
|
|
|
|
{
|
|
|
|
this.grid.toSync.push(this);
|
|
|
|
if (!skipSync)
|
|
|
|
this.grid.syncStickyHeaders();
|
|
|
|
}
|
2016-12-06 15:40:12 +03:00
|
|
|
return modified;
|
2016-05-18 18:38:22 +03:00
|
|
|
}
|
|
|
|
|
2016-05-15 11:16:50 +03:00
|
|
|
TreeGridNode.prototype._getToggleHandler = function()
|
|
|
|
{
|
|
|
|
var self = this;
|
|
|
|
if (!self._toggleHandlerClosure)
|
|
|
|
self._toggleHandlerClosure = function(e) { self._toggleHandler(); };
|
|
|
|
return self._toggleHandlerClosure;
|
|
|
|
}
|
|
|
|
|
2016-02-20 13:31:29 +03:00
|
|
|
TreeGridNode.prototype._toggleHandler = function()
|
|
|
|
{
|
|
|
|
if (!this.grid.onExpand(this))
|
|
|
|
return;
|
|
|
|
this.toggle();
|
|
|
|
}
|
|
|
|
|
2017-07-10 17:51:21 +03:00
|
|
|
TreeGridNode.prototype.toggle = function(skipHeaders)
|
2016-02-20 13:31:29 +03:00
|
|
|
{
|
|
|
|
if (this.leaf)
|
|
|
|
return;
|
|
|
|
this.collapsed = !this.collapsed;
|
2017-01-24 13:57:10 +03:00
|
|
|
(this.col_tr || this.tr)[0].cells[0].firstChild.className =
|
|
|
|
this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
2016-02-20 13:31:29 +03:00
|
|
|
if (this.collapsed)
|
|
|
|
{
|
|
|
|
// collapse all children
|
2016-10-07 18:11:29 +03:00
|
|
|
var c = this.tr[this.tr.length-1].nextSibling;
|
2016-02-24 18:48:09 +03:00
|
|
|
while (c && c._node.level > this.level)
|
2016-02-20 13:31:29 +03:00
|
|
|
{
|
|
|
|
c.style.display = 'none';
|
|
|
|
c = c.nextSibling;
|
|
|
|
}
|
2017-01-24 13:57:10 +03:00
|
|
|
if (this.col_tr)
|
|
|
|
{
|
|
|
|
c = this.col_tr[this.col_tr.length-1].nextSibling;
|
|
|
|
while (c && c._node.level > this.level)
|
|
|
|
{
|
|
|
|
c.style.display = 'none';
|
|
|
|
c = c.nextSibling;
|
|
|
|
}
|
|
|
|
}
|
2016-02-20 13:31:29 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// expand all children except children of collapsed subnodes
|
|
|
|
var st = this.children.concat([]);
|
2016-08-12 15:29:58 +03:00
|
|
|
var trs = [];
|
2016-02-20 13:31:29 +03:00
|
|
|
while (st.length)
|
|
|
|
{
|
|
|
|
var e = st.pop();
|
2016-10-07 18:11:29 +03:00
|
|
|
for (var i = 0; i < e.tr.length; i++)
|
|
|
|
{
|
|
|
|
e.tr[i].style.display = '';
|
2017-01-24 13:57:10 +03:00
|
|
|
if (this.grid.stickyHeaders)
|
|
|
|
{
|
|
|
|
e.col_tr[i].style.display = '';
|
|
|
|
this.grid.toSync.push(e);
|
|
|
|
}
|
2016-10-07 18:11:29 +03:00
|
|
|
}
|
2016-02-20 13:31:29 +03:00
|
|
|
if (!e.collapsed)
|
|
|
|
st = st.concat(e.children);
|
|
|
|
}
|
|
|
|
}
|
2017-07-10 17:51:21 +03:00
|
|
|
if (!skipHeaders)
|
|
|
|
this.grid.syncStickyHeaders();
|
2016-02-20 13:31:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
TreeGridNode.prototype.setChildren = function(isLeaf, newChildren)
|
|
|
|
{
|
|
|
|
if (!this.tr)
|
2016-10-24 14:22:07 +03:00
|
|
|
{
|
|
|
|
// root node
|
2016-02-20 13:31:29 +03:00
|
|
|
this.grid.tbody.innerHTML = '';
|
2017-01-24 13:57:10 +03:00
|
|
|
if (this.grid.fixedColBody)
|
|
|
|
this.grid.fixedColBody.innerHTML = '';
|
2016-10-24 14:22:07 +03:00
|
|
|
}
|
2016-02-20 13:31:29 +03:00
|
|
|
else
|
2016-03-04 12:27:56 +03:00
|
|
|
{
|
2016-10-07 18:11:29 +03:00
|
|
|
var tr = this.tr[this.tr.length-1];
|
|
|
|
while (tr.nextSibling && tr.nextSibling._node.level > this.level)
|
2017-01-24 13:57:10 +03:00
|
|
|
tr.parentNode.removeChild(tr.nextSibling);
|
|
|
|
if (this.col_tr)
|
|
|
|
{
|
|
|
|
tr = this.col_tr[this.col_tr.length-1];
|
|
|
|
while (tr.nextSibling && tr.nextSibling._node.level > this.level)
|
|
|
|
tr.parentNode.removeChild(tr.nextSibling);
|
|
|
|
}
|
2016-05-15 11:16:50 +03:00
|
|
|
if (this.leaf != isLeaf)
|
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
var collapser = (this.col_tr || this.tr)[0].cells[0].firstChild;
|
2016-05-15 11:16:50 +03:00
|
|
|
if (isLeaf)
|
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
collapser.className = 'collapser collapser-inactive';
|
|
|
|
removeListener(collapser, 'click', this._getToggleHandler());
|
2016-05-15 11:16:50 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
collapser.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
|
|
|
addListener(collapser, 'click', this._getToggleHandler());
|
2016-05-15 11:16:50 +03:00
|
|
|
}
|
|
|
|
}
|
2016-03-04 12:27:56 +03:00
|
|
|
}
|
2016-05-15 11:16:50 +03:00
|
|
|
this.leaf = isLeaf;
|
2016-02-20 13:31:29 +03:00
|
|
|
this.children = [];
|
2016-06-05 01:37:11 +03:00
|
|
|
this.childrenByKey = {};
|
2016-03-25 18:47:55 +03:00
|
|
|
this.addChildren(newChildren);
|
|
|
|
}
|
|
|
|
|
2016-06-05 01:37:11 +03:00
|
|
|
TreeGridNode.prototype.removeChild = function(nodeOrIndex)
|
2016-05-15 19:06:43 +03:00
|
|
|
{
|
2016-06-05 01:37:11 +03:00
|
|
|
if (nodeOrIndex instanceof TreeGridNode)
|
|
|
|
{
|
|
|
|
if (this.children[nodeOrIndex._index] == nodeOrIndex)
|
|
|
|
nodeOrIndex = nodeOrIndex._index;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!this.children[nodeOrIndex])
|
|
|
|
return false;
|
2017-01-24 13:57:10 +03:00
|
|
|
var tr = this.children[nodeOrIndex].tr[0];
|
|
|
|
var col_tr = this.children[nodeOrIndex].col_tr && this.children[nodeOrIndex].col_tr[0];
|
2016-06-05 01:37:11 +03:00
|
|
|
var l = this.children[nodeOrIndex].level;
|
2016-06-06 15:34:14 +03:00
|
|
|
if (this.children[nodeOrIndex].key !== undefined)
|
2016-06-05 01:37:11 +03:00
|
|
|
delete this.childrenByKey[this.children[nodeOrIndex].key];
|
|
|
|
this.children.splice(nodeOrIndex, 1);
|
|
|
|
for (var i = nodeOrIndex; i < this.children.length; i++)
|
|
|
|
this.children[i]._index--;
|
2017-01-24 13:57:10 +03:00
|
|
|
var k, e;
|
|
|
|
e = tr;
|
2016-05-15 19:06:43 +03:00
|
|
|
do
|
|
|
|
{
|
|
|
|
k = e;
|
|
|
|
e = e.nextSibling;
|
|
|
|
k.parentNode.removeChild(k);
|
|
|
|
} while (e && e._node.level > l);
|
2017-01-24 13:57:10 +03:00
|
|
|
if (col_tr)
|
|
|
|
{
|
|
|
|
e = col_tr;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
k = e;
|
|
|
|
e = e.nextSibling;
|
|
|
|
k.parentNode.removeChild(k);
|
|
|
|
} while (e && e._node.level > l);
|
|
|
|
}
|
2016-06-05 01:37:11 +03:00
|
|
|
return true;
|
2016-05-15 19:06:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
TreeGridNode.prototype.addChildren = function(nodes, insertBefore)
|
|
|
|
{
|
|
|
|
var e;
|
|
|
|
if (insertBefore < 0)
|
|
|
|
insertBefore = 0;
|
|
|
|
if (insertBefore === undefined || insertBefore === null || insertBefore === false ||
|
|
|
|
insertBefore >= this.children.length)
|
|
|
|
{
|
|
|
|
insertBefore = this.children.length;
|
2017-01-24 13:57:10 +03:00
|
|
|
// тут нужно получить следующий узел после текущего...
|
2016-05-15 19:06:43 +03:00
|
|
|
e = this;
|
|
|
|
while (e.children.length)
|
|
|
|
e = e.children[e.children.length-1];
|
2017-01-24 13:57:10 +03:00
|
|
|
e = e.tr && e.tr[e.tr.length-1].nextSibling ? e.tr[e.tr.length-1].nextSibling._node : null;
|
2016-05-15 19:06:43 +03:00
|
|
|
}
|
|
|
|
else
|
2017-01-24 13:57:10 +03:00
|
|
|
e = this.children[insertBefore];
|
2016-08-12 15:29:58 +03:00
|
|
|
var trs = [];
|
2016-05-15 19:06:43 +03:00
|
|
|
for (var i = 0; i < nodes.length; i++)
|
2016-06-05 01:37:11 +03:00
|
|
|
{
|
2017-01-24 13:57:10 +03:00
|
|
|
var child = new TreeGridNode(nodes[i], this.grid, this.level+1, e, this.collapsed, true);
|
2016-06-05 01:37:11 +03:00
|
|
|
child._index = insertBefore+i;
|
|
|
|
this.children.splice(insertBefore+i, 0, child);
|
2016-06-06 15:34:14 +03:00
|
|
|
if (child.key !== undefined)
|
2016-06-05 01:37:11 +03:00
|
|
|
this.childrenByKey[child.key] = child;
|
|
|
|
}
|
|
|
|
for (var i = insertBefore+nodes.length; i < this.children.length; i++)
|
|
|
|
this.children[i]._index += nodes.length;
|
2017-01-24 13:57:10 +03:00
|
|
|
this.grid.syncStickyHeaders();
|
2016-05-18 18:38:22 +03:00
|
|
|
return this.children.slice(insertBefore, nodes.length);
|
2016-02-20 13:31:29 +03:00
|
|
|
}
|
2016-08-15 16:22:13 +03:00
|
|
|
|
2016-12-06 15:40:12 +03:00
|
|
|
})();
|