jquery.tabulator
Version:
Interactive table generation plugin for jQuery UI
1,573 lines (1,190 loc) • 39 kB
JavaScript
var RowManager = function(table){
this.table = table;
this.element = $("<div class='tabulator-tableHolder' tabindex='0'></div>"); //containing element
this.tableElement = $("<div class='tabulator-table'></div>"); //table element
this.columnManager = null; //hold column manager object
this.height = 0; //hold height of table element
this.firstRender = false; //handle first render
this.renderMode = "classic"; //current rendering mode
this.rows = []; //hold row data objects
this.activeRows = []; //rows currently available to on display in the table
this.activeRowsCount = 0; //count of active rows
this.displayRows = []; //rows currently on display in the table
this.displayRowsCount = 0; //count of display rows
this.scrollTop = 0;
this.scrollLeft = 0;
this.vDomRowHeight = 20; //approximation of row heights for padding
this.vDomTop = 0; //hold position for first rendered row in the virtual DOM
this.vDomBottom = 0; //hold possition for last rendered row in the virtual DOM
this.vDomScrollPosTop = 0; //last scroll position of the vDom top;
this.vDomScrollPosBottom = 0; //last scroll position of the vDom bottom;
this.vDomTopPad = 0; //hold value of padding for top of virtual DOM
this.vDomBottomPad = 0; //hold value of padding for bottom of virtual DOM
this.vDomMaxRenderChain = 90; //the maximum number of dom elements that can be rendered in 1 go
this.vDomWindowBuffer = 0; //window row buffer before removing elements, to smooth scrolling
this.vDomWindowMinTotalRows = 20; //minimum number of rows to be generated in virtual dom (prevent buffering issues on tables with tall rows)
this.vDomWindowMinMarginRows = 5; //minimum number of rows to be generated in virtual dom margin
this.vDomTopNewRows = []; //rows to normalize after appending to optimize render speed
this.vDomBottomNewRows = []; //rows to normalize after appending to optimize render speed
};
//////////////// Setup Functions /////////////////
//return containing element
RowManager.prototype.getElement = function(){
return this.element;
};
//return table element
RowManager.prototype.getTableElement = function(){
return this.tableElement;
};
//return position of row in table
RowManager.prototype.getRowPosition = function(row, active){
if(active){
return this.activeRows.indexOf(row);
}else{
return this.rows.indexOf(row);
}
};
//link to column manager
RowManager.prototype.setColumnManager = function(manager){
this.columnManager = manager;
};
RowManager.prototype.initialize = function(){
var self = this;
self.setRenderMode();
//initialize manager
self.element.append(self.tableElement);
self.firstRender = true;
//scroll header along with table body
self.element.scroll(function(){
var left = self.element[0].scrollLeft;
//handle horizontal scrolling
if(self.scrollLeft != left){
self.columnManager.scrollHorizontal(left);
if(self.table.options.groupBy){
self.table.extensions.groupRows.scrollHeaders(left);
}
if(self.table.extExists("columnCalcs")){
self.table.extensions.columnCalcs.scrollHorizontal(left);
}
}
self.scrollLeft = left;
});
//handle virtual dom scrolling
if(this.renderMode === "virtual"){
self.element.scroll(function(){
var top = self.element[0].scrollTop;
var dir = self.scrollTop > top;
//handle verical scrolling
if(self.scrollTop != top){
self.scrollTop = top;
self.scrollVertical(dir);
if(self.table.options.ajaxProgressiveLoad == "scroll"){
self.table.extensions.ajax.nextPage(self.element[0].scrollHeight - self.element[0].clientHeight - top);
}
}else{
self.scrollTop = top;
}
});
}
};
////////////////// Row Manipulation //////////////////
RowManager.prototype.findRow = function(subject){
var self = this;
if(typeof subject == "object"){
if(subject instanceof Row){
//subject is row element
return subject;
}else if(subject instanceof RowComponent){
//subject is public row component
return subject._getSelf() || false;
}else if(subject instanceof jQuery){
//subject is a jquery element of the row
let match = self.rows.find(function(row){
return row.element === subject;
});
return match || false;
}
}else if(typeof subject == "undefined" || subject === null){
return false;
}else{
//subject should be treated as the index of the row
let match = self.rows.find(function(row){
return row.data[self.table.options.index] == subject;
});
return match || false;
}
//catch all for any other type of input
return false;
};
RowManager.prototype.getRowFromPosition = function(position, active){
if(active){
return this.activeRows[position];
}else{
return this.rows[position];
}
}
RowManager.prototype.scrollToRow = function(row, position, ifVisible){
var rowIndex = this.getDisplayRows().indexOf(row),
offset = 0;
if(rowIndex > -1){
if(typeof position === "undefined"){
position = this.table.options.scrollToRowPosition;
}
if(typeof ifVisible === "undefined"){
ifVisible = this.table.options.scrollToRowIfVisible;
}
if(position === "nearest"){
switch(this.renderMode){
case"classic":
position = Math.abs(this.element.scrollTop() - row.element.position().top) > Math.abs(this.element.scrollTop() + this.element[0].clientHeight - row.element.position().top) ? "bottom" : "top";
break;
case"virtual":
position = Math.abs(this.vDomTop - rowIndex) > Math.abs(this.vDomBottom - rowIndex) ? "bottom" : "top";
break;
}
}
//check row visibility
if(!ifVisible){
if(row.element.is(":visible")){
offset = row.element.offset().top - this.element.offset().top;
if(offset > 0 && offset < this.element[0].clientHeight - row.element.outerHeight()){
return false;
}
}
}
//scroll to row
switch(this.renderMode){
case"classic":
this.element.scrollTop(row.element.offset().top - this.element.offset().top + this.element.scrollTop());
break;
case"virtual":
this._virtualRenderFill(rowIndex, true);
break;
}
//align to correct position
switch(position){
case "middle":
case "center":
this.element.scrollTop(this.element.scrollTop() - (this.element[0].clientHeight / 2));
break;
case "bottom":
this.element.scrollTop(this.element.scrollTop() - this.element[0].clientHeight + row.getElement().outerHeight());
break;
}
return true;
}else{
console.warn("Scroll Error - Row not visible");
return false;
}
};
////////////////// Data Handling //////////////////
RowManager.prototype.setData = function(data, renderInPosition){
var self = this;
if(renderInPosition && this.getDisplayRows().length){
if(self.table.options.pagination){
self._setDataActual(data, true);
}else{
this.reRenderInPosition(function(){
self._setDataActual(data);
});
}
}else{
this.resetScroll();
this._setDataActual(data);
}
};
RowManager.prototype._setDataActual = function(data, renderInPosition){
var self = this;
self.table.options.dataLoading(data);
self.rows.forEach(function(row){
row.wipe();
});
self.rows = [];
if(this.table.options.history && this.table.extExists("history")){
this.table.extensions.history.clear();
}
if(Array.isArray(data)){
if(this.table.extExists("selectRow")){
this.table.extensions.selectRow.clearSelectionData();
}
data.forEach(function(def, i){
if(def && typeof def === "object"){
var row = new Row(def, self);
self.rows.push(row);
}else{
console.warn("Data Loading Warning - Invalid row data detected and ignored, expecting object but receved:", def);
}
});
self.table.options.dataLoaded(data);
self.refreshActiveData(false, false, renderInPosition);
}else{
console.error("Data Loading Error - Unable to process data due to invalid data type \nExpecting: array \nReceived: ", typeof data, "\nData: ", data);
}
}
RowManager.prototype.deleteRow = function(row){
var allIndex = this.rows.indexOf(row),
activeIndex = this.activeRows.indexOf(row);
if(activeIndex > -1){
this.activeRows.splice(activeIndex, 1);
}
if(allIndex > -1){
this.rows.splice(allIndex, 1);
}
this.setActiveRows(this.activeRows);
this.displayRowIterator(function(rows){
var displayIndex = rows.indexOf(row);
if(displayIndex > -1){
rows.splice(displayIndex, 1);
}
});
this.reRenderInPosition();
this.table.options.rowDeleted(row.getComponent());
this.table.options.dataEdited(this.getData());
if(this.table.options.groupBy && this.table.extExists("groupRows")){
this.table.extensions.groupRows.updateGroupRows(true);
}else if(this.table.options.pagination && this.table.extExists("page")){
this.refreshActiveData(false, false, true);
}else{
if(this.table.options.pagination && this.table.extExists("page")){
this.refreshActiveData("page");
}
}
};
RowManager.prototype.addRow = function(data, pos, index, blockRedraw){
var row = this.addRowActual(data, pos, index, blockRedraw);
if(this.table.options.history && this.table.extExists("history")){
this.table.extensions.history.action("rowAdd", row, {data:data, pos:pos, index:index});
};
return row;
};
//add multiple rows
RowManager.prototype.addRows = function(data, pos, index){
var self = this,
length = 0,
rows = [];
pos = this.findAddRowPos(pos);
if(!Array.isArray(data)){
data = [data];
}
length = data.length - 1;
if((typeof index == "undefined" && pos) || (typeof index !== "undefined" && !pos)){
data.reverse();
}
data.forEach(function(item, i){
var row = self.addRow(item, pos, index, true);
rows.push(row);
});
if(this.table.options.groupBy && this.table.extExists("groupRows")){
this.table.extensions.groupRows.updateGroupRows(true);
}else if(this.table.options.pagination && this.table.extExists("page")){
this.refreshActiveData(false, false, true);
}else{
this.reRenderInPosition();
}
//recalc column calculations if present
if(this.table.extExists("columnCalcs")){
this.table.extensions.columnCalcs.recalc(this.table.rowManager.activeRows);
}
return rows;
};
RowManager.prototype.findAddRowPos = function(pos){
if(typeof pos === "undefined"){
pos = this.table.options.addRowPos;
}
if(pos === "pos"){
pos = true;
}
if(pos === "bottom"){
pos = false;
}
return pos;
};
RowManager.prototype.addRowActual = function(data, pos, index, blockRedraw){
var row = new Row(data || {}, this),
top = this.findAddRowPos(pos),
dispRows;
if(!index && this.table.options.pagination && this.table.options.paginationAddRow == "page"){
dispRows = this.getDisplayRows();
if(top){
if(dispRows.length){
index = dispRows[0];
}else{
if(this.activeRows.length){
index = this.activeRows[this.activeRows.length-1];
top = false;
}
}
}else{
if(dispRows.length){
index = dispRows[dispRows.length - 1];
top = dispRows.length < this.table.extensions.page.getPageSize() ? false : true;
}
}
}
if(index){
index = this.findRow(index);
}
if(this.table.options.groupBy && this.table.extExists("groupRows")){
this.table.extensions.groupRows.assignRowToGroup(row);
var groupRows = row.getGroup().rows;
if(groupRows.length > 1){
if(!index || (index && groupRows.indexOf(index) == -1)){
if(top){
if(groupRows[0] !== row){
index = groupRows[0];
this._moveRowInArray(row.getGroup().rows, row, index, top);
}
}else{
if(groupRows[groupRows.length -1] !== row){
index = groupRows[groupRows.length -1];
this._moveRowInArray(row.getGroup().rows, row, index, top);
}
}
}else{
this._moveRowInArray(row.getGroup().rows, row, index, top);
}
}
};
if(index){
let allIndex = this.rows.indexOf(index),
activeIndex = this.activeRows.indexOf(index);
this.displayRowIterator(function(rows){
var displayIndex = rows.indexOf(index);
if(displayIndex > -1){
rows.splice((top ? displayIndex : displayIndex + 1), 0, row);
}
});
if(activeIndex > -1){
this.activeRows.splice((top ? activeIndex : activeIndex + 1), 0, row);
}
if(allIndex > -1){
this.rows.splice((top ? allIndex : allIndex + 1), 0, row);
}
}else{
if(top){
this.displayRowIterator(function(rows){
rows.unshift(row);
});
this.activeRows.unshift(row);
this.rows.unshift(row);
}else{
this.displayRowIterator(function(rows){
rows.push(row);
});
this.activeRows.push(row);
this.rows.push(row);
}
}
this.setActiveRows(this.activeRows);
this.table.options.rowAdded(row.getComponent());
this.table.options.dataEdited(this.getData());
if(!blockRedraw){
this.reRenderInPosition();
}
return row;
};
RowManager.prototype.moveRow = function(from, to, after){
if(this.table.options.history && this.table.extExists("history")){
this.table.extensions.history.action("rowMove", from, {pos:this.getRowPosition(from), to:to, after:after});
};
this.moveRowActual(from, to, after);
this.table.options.rowMoved(from.getComponent());
};
RowManager.prototype.moveRowActual = function(from, to, after){
var self = this;
this._moveRowInArray(this.rows, from, to, after);
this._moveRowInArray(this.activeRows, from, to, after);
this.displayRowIterator(function(rows){
self._moveRowInArray(rows, from, to, after);
});
if(this.table.options.groupBy && this.table.extExists("groupRows")){
var toGroup = to.getGroup();
var fromGroup = from.getGroup();
if(toGroup === fromGroup){
this._moveRowInArray(toGroup.rows, from, to, after);
}else{
if(fromGroup){
fromGroup.removeRow(from);
}
toGroup.insertRow(from, to, after);
}
}
};
RowManager.prototype._moveRowInArray = function(rows, from, to, after){
var fromIndex, toIndex, start, end;
if(from !== to){
fromIndex = rows.indexOf(from);
if (fromIndex > -1) {
rows.splice(fromIndex, 1);
toIndex = rows.indexOf(to);
if (toIndex > -1) {
if(after){
rows.splice(toIndex+1, 0, from);
}else{
rows.splice(toIndex, 0, from);
}
}else{
rows.splice(fromIndex, 0, from);
}
}
//restyle rows
if(rows === this.getDisplayRows()){
start = fromIndex < toIndex ? fromIndex : toIndex;
end = toIndex > fromIndex ? toIndex : fromIndex +1;
for(let i = start; i <= end; i++){
if(rows[i]){
this.styleRow(rows[i], i);
}
}
}
}
};
RowManager.prototype.clearData = function(){
this.setData([]);
};
RowManager.prototype.getRowIndex = function(row){
return this.findRowIndex(row, this.rows);
};
RowManager.prototype.getDisplayRowIndex = function(row){
var index = this.getDisplayRows().indexOf(row);
return index > -1 ? index : false;
};
RowManager.prototype.nextDisplayRow = function(row, rowOnly){
var index = this.getDisplayRowIndex(row),
nextRow = false;
if(index !== false && index < this.displayRowsCount -1){
nextRow = this.getDisplayRows()[index+1];
}
if(nextRow && (!(nextRow instanceof Row) || nextRow.type != "row")){
return this.nextDisplayRow(nextRow, rowOnly);
}
return nextRow;
};
RowManager.prototype.prevDisplayRow = function(row, rowOnly){
var index = this.getDisplayRowIndex(row),
prevRow = false;
if(index){
prevRow = this.getDisplayRows()[index-1];
}
if(prevRow && (!(prevRow instanceof Row) || prevRow.type != "row")){
return this.prevDisplayRow(prevRow, rowOnly);
}
return prevRow;
};
RowManager.prototype.findRowIndex = function(row, list){
var rowIndex;
row = this.findRow(row);
if(row){
rowIndex = list.indexOf(row);
if(rowIndex > -1){
return rowIndex;
}
}
return false;
};
RowManager.prototype.getData = function(active, transform){
var self = this,
output = [];
var rows = active ? self.activeRows : self.rows;
rows.forEach(function(row){
output.push(row.getData(transform || "data"));
});
return output;
};
RowManager.prototype.getHtml = function(active){
var data = this.getData(active),
columns = [],
header = "",
body = "",
table = "";
//build header row
this.table.columnManager.getColumns().forEach(function(column){
var def = column.getDefinition();
if(column.visible && !def.hideInHtml){
header += `<th>${(def.title || "")}</th>`;
columns.push(column);
}
})
//build body rows
data.forEach(function(rowData){
var row = "";
columns.forEach(function(column){
var value = column.getFieldValue(rowData);
if(typeof value === "undefined" || value === null){
value = ":";
}
row += `<td>${value}</td>`;
});
body += `<tr>${row}</tr>`;
});
//build table
table = `<table>
<thead>
<tr>${header}</tr>
</thead>
<tbody>${body}</tbody>
</table>`;
return table;
};
RowManager.prototype.getComponents = function(active){
var self = this,
output = [];
var rows = active ? self.activeRows : self.rows;
rows.forEach(function(row){
output.push(row.getComponent());
});
return output;
}
RowManager.prototype.getDataCount = function(active){
return active ? this.rows.length : this.activeRows.length;
};
RowManager.prototype._genRemoteRequest = function(){
var self = this,
table = self.table,
options = table.options,
params = {};
if(table.extExists("page")){
//set sort data if defined
if(options.ajaxSorting){
let sorters = self.table.extensions.sort.getSort();
sorters.forEach(function(item){
delete item.column;
});
params[self.table.extensions.page.paginationDataSentNames.sorters] = sorters;
}
//set filter data if defined
if(options.ajaxFiltering){
let filters = self.table.extensions.filter.getFilters(true, true);
params[self.table.extensions.page.paginationDataSentNames.filters] = filters;
}
self.table.extensions.ajax.setParams(params, true);
}
table.extensions.ajax.sendRequest(function(data){
self.setData(data);
});
};
//choose the path to refresh data after a filter update
RowManager.prototype.filterRefresh = function(){
var table = this.table,
options = table.options,
left = this.scrollLeft;
if(options.ajaxFiltering){
if(options.pagination == "remote" && table.extExists("page")){
table.extensions.page.reset(true);
table.extensions.page.setPage(1);
}else{
//assume data is url, make ajax call to url to get data
this._genRemoteRequest();
}
}else{
this.refreshActiveData("filter");
}
this.scrollHorizontal(left);
};
//choose the path to refresh data after a sorter update
RowManager.prototype.sorterRefresh = function(){
var table = this.table,
options = this.table.options,
left = this.scrollLeft;
if(options.ajaxSorting){
if(options.pagination == "remote" && table.extExists("page")){
table.extensions.page.reset(true);
table.extensions.page.setPage(1);
}else{
//assume data is url, make ajax call to url to get data
this._genRemoteRequest();
}
}else{
this.refreshActiveData("sort");
}
this.scrollHorizontal(left);
};
RowManager.prototype.scrollHorizontal = function(left){
this.scrollLeft = left;
this.element.scrollLeft(left);
if(this.table.options.groupBy){
this.table.extensions.groupRows.scrollHeaders(left);
}
if(this.table.extExists("columnCalcs")){
this.table.extensions.columnCalcs.scrollHorizontal(left);
}
};
//set active data set
RowManager.prototype.refreshActiveData = function(stage, skipStage, renderInPosition){
var self = this,
table = this.table,
displayIndex;
if(!stage){
stage = "all";
}
if(table.options.selectable && !table.options.selectablePersistence && table.extExists("selectRow")){
table.extensions.selectRow.deselectRows();
}
//cascade through data refresh stages
switch(stage){
case "all":
case "filter":
if(!skipStage){
if(table.extExists("filter")){
self.setActiveRows(table.extensions.filter.filter(self.rows));
}else{
self.setActiveRows(self.rows.slice(0));
}
}else{
skipStage = false;
}
case "sort":
if(!skipStage){
if(table.extExists("sort")){
table.extensions.sort.sort();
}
}else{
skipStage = false;
}
//generic stage to allow for pipeline trigger after the data manipulation stage
case "display":
this.resetDisplayRows();
case "freeze":
if(!skipStage){
if(this.table.extExists("frozenRows")){
if(table.extensions.frozenRows.isFrozen()){
if(!table.extensions.frozenRows.getDisplayIndex()){
table.extensions.frozenRows.setDisplayIndex(this.getNextDisplayIndex());
}
displayIndex = table.extensions.frozenRows.getDisplayIndex();
displayIndex = self.setDisplayRows(table.extensions.frozenRows.getRows(this.getDisplayRows(displayIndex - 1)), displayIndex);
if(displayIndex !== true){
table.extensions.frozenRows.setDisplayIndex(displayIndex);
}
}
}
}else{
skipStage = false;
}
case "group":
if(!skipStage){
if(table.options.groupBy && table.extExists("groupRows")){
if(!table.extensions.groupRows.getDisplayIndex()){
table.extensions.groupRows.setDisplayIndex(this.getNextDisplayIndex());
}
displayIndex = table.extensions.groupRows.getDisplayIndex();
displayIndex = self.setDisplayRows(table.extensions.groupRows.getRows(this.getDisplayRows(displayIndex - 1)), displayIndex);
if(displayIndex !== true){
table.extensions.groupRows.setDisplayIndex(displayIndex);
}
}
}else{
skipStage = false;
}
if(table.options.pagination && table.extExists("page") && !renderInPosition){
if(table.extensions.page.getMode() == "local"){
table.extensions.page.reset();
}
}
case "page":
if(!skipStage){
if(table.options.pagination && table.extExists("page")){
if(!table.extensions.page.getDisplayIndex()){
table.extensions.page.setDisplayIndex(this.getNextDisplayIndex());
}
displayIndex = table.extensions.page.getDisplayIndex();
if(table.extensions.page.getMode() == "local"){
table.extensions.page.setMaxRows(this.getDisplayRows(displayIndex - 1).length);
}
displayIndex = self.setDisplayRows(table.extensions.page.getRows(this.getDisplayRows(displayIndex - 1)), displayIndex);
if(displayIndex !== true){
table.extensions.page.setDisplayIndex(displayIndex);
}
}
}else{
skipStage = false;
}
}
if(self.element.is(":visible")){
if(renderInPosition){
self.reRenderInPosition();
}else{
self.renderTable();
if(table.options.layoutColumnsOnNewData){
self.table.columnManager.redraw(true);
}
}
}
if(table.extExists("columnCalcs")){
table.extensions.columnCalcs.recalc(this.activeRows);
}
};
RowManager.prototype.setActiveRows = function(activeRows){
this.activeRows = activeRows;
this.activeRowsCount = this.activeRows.length;
};
//reset display rows array
RowManager.prototype.resetDisplayRows = function(){
this.displayRows = [];
this.displayRows.push(this.activeRows.slice(0));
this.displayRowsCount = this.displayRows[0].length;
if(this.table.extExists("frozenRows")){
this.table.extensions.frozenRows.setDisplayIndex(0);
}
if(this.table.options.groupBy && this.table.extExists("groupRows")){
this.table.extensions.groupRows.setDisplayIndex(0);
}
if(this.table.options.pagination && this.table.extExists("page")){
this.table.extensions.page.setDisplayIndex(0);
}
}
RowManager.prototype.getNextDisplayIndex = function(){
return this.displayRows.length;
}
//set display row pipeline data
RowManager.prototype.setDisplayRows = function(displayRows, index){
var output = true;
if(index && typeof this.displayRows[index] != "undefined"){
this.displayRows[index] = displayRows;
output = true;
}else{
this.displayRows.push(displayRows);
output = index = this.displayRows.length -1;
}
if(index == this.displayRows.length -1){
this.displayRowsCount = this.displayRows[this.displayRows.length -1].length;
}
return output;
};
RowManager.prototype.getDisplayRows = function(index){
if(typeof index == "undefined"){
return this.displayRows.length ? this.displayRows[this.displayRows.length -1] : [];
}else{
return this.displayRows[index] || [];
}
}
//repeat action accross display rows
RowManager.prototype.displayRowIterator = function(callback){
this.displayRows.forEach(callback);
this.displayRowsCount = this.displayRows[this.displayRows.length -1].length;
}
//return only actual rows (not group headers etc)
RowManager.prototype.getRows = function(){
return this.rows;
};
///////////////// Table Rendering /////////////////
//trigger rerender of table in current position
RowManager.prototype.reRenderInPosition = function(callback){
if(this.getRenderMode() == "virtual"){
var scrollTop = this.element.scrollTop();
var topRow = false;
var topOffset = false;
var left = this.scrollLeft;
var rows = this.getDisplayRows();
for(var i = this.vDomTop; i <= this.vDomBottom; i++){
if(rows[i]){
var diff = scrollTop - rows[i].getElement().position().top;
if(topOffset === false || Math.abs(diff) < topOffset){
topOffset = diff;
topRow = i;
}else{
break;
}
}
}
if(callback){
callback();
}
this._virtualRenderFill((topRow === false ? this.displayRowsCount - 1 : topRow), true, topOffset || 0);
this.scrollHorizontal(left);
}else{
this.renderTable();
}
};
RowManager.prototype.setRenderMode = function(){
if((this.table.element.innerHeight() || this.table.options.height) && this.table.options.virtualDom){
this.renderMode = "virtual";
}else{
this.renderMode = "classic";
}
}
RowManager.prototype.getRenderMode = function(){
return this.renderMode;
};
RowManager.prototype.renderTable = function(){
var self = this;
self.table.options.renderStarted();
self.element.scrollTop(0);
switch(self.renderMode){
case "classic":
self._simpleRender();
break;
case "virtual":
self._virtualRenderFill();
break;
}
if(self.firstRender){
if(self.displayRowsCount){
self.firstRender = false;
self.table.extensions.layout.layout();
}else{
self.renderEmptyScroll();
}
}
if(self.table.extExists("frozenColumns")){
self.table.extensions.frozenColumns.layout();
}
if(!self.displayRowsCount){
if(self.table.options.placeholder){
if(this.renderMode){
self.table.options.placeholder.attr("tabulator-render-mode", this.renderMode)
}
self.getElement().append(self.table.options.placeholder);
}
}
self.table.options.renderComplete();
};
//simple render on heightless table
RowManager.prototype._simpleRender = function(){
var self = this,
element = this.tableElement;
self._clearVirtualDom();
if(self.displayRowsCount){
var onlyGroupHeaders = true;
self.getDisplayRows().forEach(function(row, index){
self.styleRow(row, index);
element.append(row.getElement());
row.initialize(true);
if(row.type !== "group"){
onlyGroupHeaders = false;
}
});
if(onlyGroupHeaders){
self.tableElement.css({
"min-width":self.table.columnManager.getWidth(),
});
}
}else{
self.renderEmptyScroll();
}
};
//show scrollbars on empty table div
RowManager.prototype.renderEmptyScroll = function(){
var self = this;
self.tableElement.css({
"min-width":self.table.columnManager.getWidth(),
"min-height":"1px",
"visibility":"hidden",
});
};
RowManager.prototype._clearVirtualDom = function(){
var element = this.tableElement;
if(this.table.options.placeholder){
this.table.options.placeholder.detach();
}
element.children().detach();
element.css({
"padding-top":"",
"padding-bottom":"",
"min-width":"",
"min-height":"",
"visibility":"",
});
this.scrollTop = 0;
this.scrollLeft = 0;
this.vDomTop = 0;
this.vDomBottom = 0;
this.vDomTopPad = 0;
this.vDomBottomPad = 0;
};
RowManager.prototype.styleRow = function(row, index){
if(index % 2){
row.element.addClass("tabulator-row-even").removeClass("tabulator-row-odd");
}else{
row.element.addClass("tabulator-row-odd").removeClass("tabulator-row-even");
}
};
//full virtual render
RowManager.prototype._virtualRenderFill = function(position, forceMove, offset){
var self = this,
element = self.tableElement,
holder = self.element,
topPad = 0,
rowsHeight = 0,
topPadHeight = 0,
i = 0,
rows = self.getDisplayRows();
position = position || 0;
offset = offset || 0;
if(!position){
self._clearVirtualDom();
}else{
element.children().detach();
//check if position is too close to bottom of table
let heightOccpied = (self.displayRowsCount - position + 1) * self.vDomRowHeight
if(heightOccpied < self.height){
position -= Math.ceil((self.height - heightOccpied) / self.vDomRowHeight);
if(position < 0){
position = 0;
}
}
//calculate initial pad
topPad = Math.min(Math.max(Math.floor(self.vDomWindowBuffer / self.vDomRowHeight), self.vDomWindowMinMarginRows), position);
position -= topPad;
}
if(self.displayRowsCount && self.element.is(":visible")){
self.vDomTop = position;
self.vDomBottom = position -1;
while ((rowsHeight <= self.height + self.vDomWindowBuffer || i < self.vDomWindowMinTotalRows) && self.vDomBottom < self.displayRowsCount -1){
var index = self.vDomBottom + 1,
row = rows[index];
self.styleRow(row, index);
element.append(row.getElement());
if(!row.initialized){
row.initialize(true);
}else{
if(!row.heightInitialized){
row.normalizeHeight(true);
}
}
if(i < topPad){
topPadHeight += row.getHeight();
}else{
rowsHeight += row.getHeight();
}
self.vDomBottom ++;
i++;
}
if(!position){
this.vDomTopPad = 0;
//adjust rowheight to match average of rendered elements
self.vDomRowHeight = Math.floor((rowsHeight + topPadHeight) / i);
self.vDomBottomPad = self.vDomRowHeight * (self.displayRowsCount - self.vDomBottom -1);
self.vDomScrollHeight = topPadHeight + rowsHeight + self.vDomBottomPad - self.height;
}else{
self.vDomTopPad = !forceMove ? self.scrollTop - topPadHeight : (self.vDomRowHeight * this.vDomTop) + offset;
self.vDomBottomPad = self.vDomBottom == self.displayRowsCount-1 ? 0 : Math.max(self.vDomScrollHeight - self.vDomTopPad - rowsHeight - topPadHeight, 0);
}
element[0].style.paddingTop = self.vDomTopPad + "px";
element[0].style.paddingBottom = self.vDomBottomPad + "px";
if(forceMove){
this.scrollTop = self.vDomTopPad + (topPadHeight) + offset;
}
this.scrollTop = Math.min(this.scrollTop, this.element[0].scrollHeight - this.height);
//adjust for horizontal scrollbar if present
if(this.element[0].scrollWidth > this.element[0].offsetWidt){
this.scrollTop += this.element[0].offsetHeight - this.element[0].clientHeight;
}
this.vDomScrollPosTop = this.scrollTop;
this.vDomScrollPosBottom = this.scrollTop;
holder.scrollTop(this.scrollTop);
if(self.table.options.groupBy){
if(self.table.extensions.layout.getMode() != "fitDataFill" && self.displayRowsCount == self.table.extensions.groupRows.countGroups()){
self.tableElement.css({
"min-width":self.table.columnManager.getWidth(),
});
}
}
}else{
this.renderEmptyScroll();
}
};
//handle vertical scrolling
RowManager.prototype.scrollVertical = function(dir){
var topDiff = this.scrollTop - this.vDomScrollPosTop;
var bottomDiff = this.scrollTop - this.vDomScrollPosBottom;
var margin = this.vDomWindowBuffer * 2;
if(-topDiff > margin || bottomDiff > margin){
//if big scroll redraw table;
var left = this.scrollLeft;
this._virtualRenderFill(Math.floor((this.element[0].scrollTop / this.element[0].scrollHeight) * this.displayRowsCount));
this.scrollHorizontal(left);
}else{
if(dir){
//scrolling up
if(topDiff < 0){
this._addTopRow(-topDiff)
}
if(topDiff < 0){
//hide bottom row if needed
if(this.vDomScrollHeight - this.scrollTop > this.vDomWindowBuffer){
this._removeBottomRow(-bottomDiff);
}
}
}else{
//scrolling down
if(topDiff >= 0){
//hide top row if needed
if(this.scrollTop > this.vDomWindowBuffer){
this._removeTopRow(topDiff);
}
}
if(bottomDiff >= 0){
this._addBottomRow(bottomDiff);
}
}
}
};
RowManager.prototype._addTopRow = function(topDiff, i=0){
var table = this.tableElement,
rows = this.getDisplayRows();
if(this.vDomTop){
let index = this.vDomTop -1,
topRow = rows[index],
topRowHeight = topRow.getHeight() || this.vDomRowHeight;
//hide top row if needed
if(topDiff >= topRowHeight){
this.styleRow(topRow, index);
table.prepend(topRow.getElement());
if(!topRow.initialized || !topRow.heightInitialized){
this.vDomTopNewRows.push(topRow);
if(!topRow.heightInitialized){
topRow.clearCellHeight();
}
}
topRow.initialize();
this.vDomTopPad -= topRowHeight;
if(this.vDomTopPad < 0){
this.vDomTopPad = index * this.vDomRowHeight;
}
if(!index){
this.vDomTopPad = 0;
}
table[0].style.paddingTop = this.vDomTopPad + "px";
this.vDomScrollPosTop -= topRowHeight;
this.vDomTop--;
}
topDiff = -(this.scrollTop - this.vDomScrollPosTop);
if(i < this.vDomMaxRenderChain && this.vDomTop && topDiff >= (rows[this.vDomTop -1].getHeight() || this.vDomRowHeight)){
this._addTopRow(topDiff, i+1);
}else{
this._quickNormalizeRowHeight(this.vDomTopNewRows);
}
}
};
RowManager.prototype._removeTopRow = function(topDiff){
var table = this.tableElement,
topRow = this.getDisplayRows()[this.vDomTop],
topRowHeight = topRow.getHeight() || this.vDomRowHeight;
if(topDiff >= topRowHeight){
topRow.element.detach();
this.vDomTopPad += topRowHeight;
table[0].style.paddingTop = this.vDomTopPad + "px";
this.vDomScrollPosTop += this.vDomTop ? topRowHeight : topRowHeight + this.vDomWindowBuffer;
this.vDomTop++;
topDiff = this.scrollTop - this.vDomScrollPosTop;
this._removeTopRow(topDiff);
}
};
RowManager.prototype._addBottomRow = function(bottomDiff, i=0){
var table = this.tableElement,
rows = this.getDisplayRows();
if(this.vDomBottom < this.displayRowsCount -1){
let index = this.vDomBottom + 1,
bottomRow = rows[index],
bottomRowHeight = bottomRow.getHeight() || this.vDomRowHeight;
//hide bottom row if needed
if(bottomDiff >= bottomRowHeight){
this.styleRow(bottomRow, index);
table.append(bottomRow.getElement());
if(!bottomRow.initialized || !bottomRow.heightInitialized){
this.vDomBottomNewRows.push(bottomRow);
if(!bottomRow.heightInitialized){
bottomRow.clearCellHeight();
}
}
bottomRow.initialize();
this.vDomBottomPad -= bottomRowHeight;
if(this.vDomBottomPad < 0 || index == this.displayRowsCount -1){
this.vDomBottomPad = 0;
}
table[0].style.paddingBottom = this.vDomBottomPad + "px";
this.vDomScrollPosBottom += bottomRowHeight;
this.vDomBottom++;
}
bottomDiff = this.scrollTop - this.vDomScrollPosBottom;
if(i < this.vDomMaxRenderChain && this.vDomBottom < this.displayRowsCount -1 && bottomDiff >= (rows[this.vDomBottom + 1].getHeight() || this.vDomRowHeight)){
this._addBottomRow(bottomDiff, i+1);
}else{
this._quickNormalizeRowHeight(this.vDomBottomNewRows);
}
}
};
RowManager.prototype._removeBottomRow = function(bottomDiff){
var table = this.tableElement,
bottomRow = this.getDisplayRows()[this.vDomBottom],
bottomRowHeight = bottomRow.getHeight() || this.vDomRowHeight;
if(bottomDiff >= bottomRowHeight){
bottomRow.element.detach();
this.vDomBottomPad += bottomRowHeight;
if(this.vDomBottomPad < 0){
this.vDomBottomPad == 0;
}
table[0].style.paddingBottom = this.vDomBottomPad + "px";
this.vDomScrollPosBottom -= bottomRowHeight;
this.vDomBottom--;
bottomDiff = -(this.scrollTop - this.vDomScrollPosBottom);
this._removeBottomRow(bottomDiff);
}
};
RowManager.prototype._quickNormalizeRowHeight = function(rows){
rows.forEach(function(row){
row.calcHeight();
});
rows.forEach(function(row){
row.setCellHeight();
});
rows.length = 0;
};
//normalize height of active rows
RowManager.prototype.normalizeHeight = function(){
var self = this;
self.activeRows.forEach(function(row){
row.normalizeHeight();
});
};
//adjust the height of the table holder to fit in the Tabulator element
RowManager.prototype.adjustTableSize = function(){
var self = this;
if(this.renderMode === "virtual"){
self.height = self.element.innerHeight();
self.vDomWindowBuffer = self.table.options.virtualDomBuffer || self.height;
let otherHeight = self.columnManager.getElement().outerHeight() + (self.table.footerManager ? self.table.footerManager.getElement().outerHeight() : 0);
self.element.css({
"min-height":"calc(100% - " + otherHeight + "px)",
"height":"calc(100% - " + otherHeight + "px)",
"max-height":"calc(100% - " + otherHeight + "px)",
});
}
};
//renitialize all rows
RowManager.prototype.reinitialize = function(){
this.rows.forEach(function(row){
row.reinitialize();
});
};
//redraw table
RowManager.prototype.redraw = function (force){
var pos = 0,
left = this.scrollLeft;
this.adjustTableSize();
if(!force){
if(self.renderMode == "classic"){
if(self.table.options.groupBy){
self.refreshActiveData("group", false, false);
}else{
this._simpleRender();
}
}else{
this.reRenderInPosition();
this.scrollHorizontal(left);
}
if(!this.displayRowsCount){
if(this.table.options.placeholder){
this.getElement().append(this.table.options.placeholder);
}
}
}else{
this.renderTable();
}
};
RowManager.prototype.resetScroll = function(){
this.element.scrollLeft(0);
this.element.scrollTop(0);
this.element.scroll();
};