| Current File : /home/jvzmxxx/wiki1/extensions/InteractiveTimeline/chap-links-library/js/src/treegrid/smarttable.js |
/**
* SmartTable
*
* SmartTable is a javaScript library for rendering big tables from JSON data.
* A browser can only render a limited amount of data. SmartTable lets the
* browser only render the currently visible rows of the table, which remains
* fast also for large amounts of data.
*
* @license
* This json editor is open sourced with the intention to use the editor as
* a component in your own application. Not to just copy and monetize the editor
* as it is.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* Copyright (C) 2012 Almende <http://www.almende.com>
*
* @author Jos de Jong <jos@almende.org>
* @date 2012-02-15
*/
/*
- drastically improve performance
- make more generic, not only for table
- do not base on JSON, but just on provided TR's, which can be rendered or kept
out of the interface
*/
var SmartTable = function (container) {
this.container = container;
// start and end index of the visible rows
this.offset = 0;
this.limit = 0;
this.dom = {
'rows': []
};
this._createDom();
this.setData([], {});
}
/**
* Set data
* @param {Array} an array with arrays or objects
*/
SmartTable.prototype.setData = function (data, options) {
this.data = data || [];
this.options = options || {};
// TODO: create a header from the data?
this.redraw();
}
/**
* Create the dom of the table
*/
SmartTable.prototype._createDom = function () {
if (!this.container) {
throw 'No HTML container defined';
}
this.dom.topSpace = document.createElement('div');
this.dom.topSpace.style.height = '0px';
this.container.appendChild(this.dom.topSpace);
this.dom.table = document.createElement('table');
this.dom.tbody = document.createElement('tbody');
this.dom.table.appendChild(this.dom.tbody);
this.container.appendChild(this.dom.table);
this.dom.bottomSpace = document.createElement('div');
this.dom.bottomSpace.style.height = '0px';
this.container.appendChild(this.dom.bottomSpace);
}
/**
* Create a row from given data
* @param {Array} row
* @return {HTMLElement} domRow
*/
SmartTable.prototype.createRow = function (row) {
var domRow = document.createElement('tr');
for (var i = 0; i < row.length; i++) {
var td = document.createElement('td');
td.innerHTML = row[i];
domRow.appendChild(td);
}
return domRow;
}
/**
* Redraw the table for the current visible window
*/
SmartTable.prototype.redraw = function () {
if (!this.container) {
throw 'No HTML container defined';
}
//console.log('redraw');
var data = this.data;
var length = data.length;
var defaultHeight = 24; // TODO: defaultHeight from options
// calculate the current visible window
var scrollTop = this.container.scrollTop; // TODO: check if supported in IE
var height = this.container.clientHeight;
var absTop = scrollTop;
var absBottom = scrollTop + height;
var newOffset = this.offset;
var newLimit = this.limit;
//console.log(newOffset, newOffset + newLimit, 'initial ');
// while start row < visible area, remove row
var row = this.dom.rows[newOffset];
var topHeight = row ? this.dom.topSpace.clientHeight : 0;
while (newLimit > 0 && row && (topHeight + row.clientHeight < absTop)) {
var oldTableHeight = this.dom.table.clientHeight;
this.dom.tbody.removeChild(row);
delete this.dom.rows[newOffset];
var newTableHeight = this.dom.table.clientHeight;
var rowHeight = (oldTableHeight - newTableHeight);
scrollTop -= (rowHeight - defaultHeight);
topHeight += defaultHeight;
this.dom.topSpace.style.height = topHeight + 'px';
newOffset++;
newLimit--;
row = this.dom.rows[newOffset];
}
//console.log(newOffset, newOffset + newLimit, 'top rows removed ');
// while end row > visible area, remove row
var row = this.dom.rows[newOffset + newLimit - 1];
while (newLimit > 0 && row && topHeight + row.offsetTop > absBottom) {
this.dom.tbody.removeChild(row);
delete this.dom.rows[newOffset + newLimit - 1];
newLimit--;
row = this.dom.rows[newOffset + newLimit - 1];
}
//console.log(newOffset, newOffset + newLimit, 'bottom rows removed ');
// scrollTop can be changed
var absTop = scrollTop;
var absBottom = scrollTop + height;
// while start row > visible area, add row before
var row = this.dom.rows[newOffset];
while (newOffset > 0 && row && (topHeight > absTop)) {
var oldTableHeight = this.dom.table.clientHeight;
newRow = this.createRow(data[newOffset - 1]);
this.dom.rows[newOffset - 1] = newRow;
this.dom.tbody.insertBefore(newRow, row);
var newTableHeight = this.dom.table.clientHeight;
var rowHeight = (newTableHeight - oldTableHeight);
scrollTop += (rowHeight - defaultHeight);
topHeight -= defaultHeight;
this.dom.topSpace.style.height = topHeight + 'px';
newOffset--;
newLimit++;
row = newRow;
}
//console.log(newOffset, newOffset + newLimit, 'top rows added ');
// scrollTop can be changed
var absTop = scrollTop;
var absBottom = scrollTop + height;
if (newLimit == 0) {
// jump downwards
while (newOffset < length && topHeight + defaultHeight < absTop) {
newOffset++;
topHeight += defaultHeight;
}
// jump upwards
while (newOffset > 0 && topHeight > absTop) {
newOffset--;
topHeight -= defaultHeight;
}
this.dom.topSpace.style.height = topHeight + 'px';
//console.log(newOffset, newOffset + newLimit, 'jumped ');
}
// while end row < visible area, add row
var row = this.dom.rows[newOffset + newLimit - 1];
while ((newOffset + newLimit < length) &&
(!row || topHeight + row.offsetTop + row.clientHeight < absBottom)) {
row = this.createRow(data[newOffset + newLimit]);
this.dom.rows[newOffset + newLimit] = row;
this.dom.tbody.appendChild(row);
newLimit++;
}
// console.log(newOffset, newOffset + newLimit, 'bottom rows added ');
this.dom.bottomSpace.style.height = (length - (newOffset + newLimit)) * defaultHeight + 'px';
this.container.scrollTop = scrollTop;
this.offset = newOffset;
this.limit = newLimit;
/* TODO
var me = this;
addEventListener(this.container, 'mousewheel', function (event) {
me.redraw();
});
//*/
//console.log(this.offset, 'to', this.offset + this.limit, 'bottom rows added ');
}
/**
* Add and event listener. Works for all browsers
* @param {DOM Element} element An html element
* @param {string} action The action, for example 'click',
* without the prefix 'on'
* @param {function} listener The callback function to be executed
* @param {boolean} useCapture
*/
addEventListener = function (element, action, listener, useCapture) {
if (element.addEventListener) {
if (useCapture === undefined) {
useCapture = false;
}
if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) {
action = 'DOMMouseScroll'; // For Firefox
}
element.addEventListener(action, listener, useCapture);
} else {
element.attachEvent('on' + action, listener); // IE browsers
}
};