tabulator-tables
Version:
Interactive table generation JavaScript library
612 lines (465 loc) • 15.8 kB
JavaScript
import Module from '../../core/Module.js';
import Group from './Group.js';
class GroupRows extends Module{
constructor(table){
super(table);
this.groupIDLookups = false; //enable table grouping and set field to group by
this.startOpen = [function(){return false;}]; //starting state of group
this.headerGenerator = [function(){return "";}];
this.groupList = []; //ordered list of groups
this.allowedValues = false;
this.groups = {}; //hold row groups
this.displayIndex = 0; //index in display pipeline
this.displayHandler = this.getRows.bind(this);
//register table options
this.registerTableOption("groupBy", false); //enable table grouping and set field to group by
this.registerTableOption("groupStartOpen", true); //starting state of group
this.registerTableOption("groupValues", false);
this.registerTableOption("groupUpdateOnCellEdit", false);
this.registerTableOption("groupHeader", false); //header generation function
this.registerTableOption("groupHeaderPrint", null);
this.registerTableOption("groupHeaderClipboard", null);
this.registerTableOption("groupHeaderHtmlOutput", null);
this.registerTableOption("groupHeaderDownload", null);
this.registerTableOption("groupToggleElement", "arrow");
this.registerTableOption("groupClosedShowCalcs", false);
//register table functions
this.registerTableFunction("setGroupBy", this.setGroupBy.bind(this));
this.registerTableFunction("setGroupValues", this.setGroupValues.bind(this));
this.registerTableFunction("setGroupStartOpen", this.setGroupStartOpen.bind(this));
this.registerTableFunction("setGroupHeader", this.setGroupHeader.bind(this));
this.registerTableFunction("getGroups", this.userGetGroups.bind(this));
this.registerTableFunction("getGroupedData", this.userGetGroupedData.bind(this));
//register component functions
this.registerComponentFunction("row", "getGroup", this.rowGetGroup.bind(this));
}
//initialize group configuration
initialize(){
if(this.table.options.groupBy){
if(this.table.options.groupUpdateOnCellEdit){
this.subscribe("cell-value-updated", this.cellUpdated.bind(this));
this.subscribe("row-data-changed", this.reassignRowToGroup.bind(this), 0);
}
this.subscribe("table-built", this.configureGroupSetup.bind(this));
this.subscribe("row-deleting", this.rowDeleting.bind(this));
this.subscribe("row-deleted", this.rowsUpdated.bind(this));
this.subscribe("scroll-horizontal", this.scrollHeaders.bind(this));
this.subscribe("rows-wipe", this.wipe.bind(this));
this.subscribe("rows-added", this.rowsUpdated.bind(this));
this.subscribe("row-moving", this.rowMoving.bind(this));
this.subscribe("row-adding-index", this.rowAddingIndex.bind(this));
this.subscribe("rows-sample", this.rowSample.bind(this));
this.subscribe("render-virtual-fill", this.virtualRenderFill.bind(this));
this.registerDisplayHandler(this.displayHandler, 20);
this.initialized = true;
}
}
configureGroupSetup(){
if(this.table.options.groupBy){
var groupBy = this.table.options.groupBy,
startOpen = this.table.options.groupStartOpen,
groupHeader = this.table.options.groupHeader;
this.allowedValues = this.table.options.groupValues;
if(Array.isArray(groupBy) && Array.isArray(groupHeader) && groupBy.length > groupHeader.length){
console.warn("Error creating group headers, groupHeader array is shorter than groupBy array");
}
this.headerGenerator = [function(){return "";}];
this.startOpen = [function(){return false;}]; //starting state of group
this.langBind("groups|item", (langValue, lang) => {
this.headerGenerator[0] = (value, count, data) => { //header layout function
return (typeof value === "undefined" ? "" : value) + "<span>(" + count + " " + ((count === 1) ? langValue : lang.groups.items) + ")</span>";
};
});
this.groupIDLookups = [];
if(Array.isArray(groupBy)){
if(this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "table" && this.table.options.columnCalcs != "both"){
this.table.modules.columnCalcs.removeCalcs();
}
}else{
if(this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "group"){
var cols = this.table.columnManager.getRealColumns();
cols.forEach((col) => {
if(col.definition.topCalc){
this.table.modules.columnCalcs.initializeTopRow();
}
if(col.definition.bottomCalc){
this.table.modules.columnCalcs.initializeBottomRow();
}
});
}
}
if(!Array.isArray(groupBy)){
groupBy = [groupBy];
}
groupBy.forEach((group, i) => {
var lookupFunc, column;
if(typeof group == "function"){
lookupFunc = group;
}else{
column = this.table.columnManager.getColumnByField(group);
if(column){
lookupFunc = function(data){
return column.getFieldValue(data);
};
}else{
lookupFunc = function(data){
return data[group];
};
}
}
this.groupIDLookups.push({
field: typeof group === "function" ? false : group,
func:lookupFunc,
values:this.allowedValues ? this.allowedValues[i] : false,
});
});
if(startOpen){
if(!Array.isArray(startOpen)){
startOpen = [startOpen];
}
startOpen.forEach((level) => {
level = typeof level == "function" ? level : function(){return true;};
});
this.startOpen = startOpen;
}
if(groupHeader){
this.headerGenerator = Array.isArray(groupHeader) ? groupHeader : [groupHeader];
}
}else{
this.groupList = [];
this.groups = {};
}
}
rowSample(rows, prevValue){
var group = this.getGroups(false)[0];
prevValue.push(group.getRows(false)[0]);
return prevValue;
}
virtualRenderFill(){
var el = this.table.rowManager.tableElement;
var rows = this.table.rowManager.getVisibleRows();
rows = rows.filter((row) => {
return row.type !== "group";
});
el.style.minWidth = !rows.length ? this.table.columnManager.getWidth() + "px" : "";
// if(this.table.options.groupBy){
// if(this.layoutMode() != "fitDataFill" && rowsCount == this.table.modules.groupRows.countGroups()){
// el.style.minWidth = this.table.columnManager.getWidth() + "px";
// }
// }
}
rowAddingIndex(row, index, top){
this.assignRowToGroup(row);
var groupRows = row.modules.group.rows;
if(groupRows.length > 1){
if(!index || (index && groupRows.indexOf(index) == -1)){
if(top){
if(groupRows[0] !== row){
index = groupRows[0];
this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
}
}else{
if(groupRows[groupRows.length -1] !== row){
index = groupRows[groupRows.length -1];
this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
}
}
}else{
this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
}
}
return index;
}
trackChanges(){
this.dispatch("group-changed");
}
///////////////////////////////////
///////// Table Functions /////////
///////////////////////////////////
setGroupBy(groups){
this.table.options.groupBy = groups;
if(!this.initialized){
this.initialize();
}
this.configureGroupSetup();
this.refreshData();
this.trackChanges();
}
setGroupValues(groupValues){
this.table.options.groupValues = groupValues;
this.configureGroupSetup();
this.refreshData();
this.trackChanges();
}
setGroupStartOpen(values){
this.table.options.groupStartOpen = values;
this.configureGroupSetup();
if(this.table.options.groupBy){
this.refreshData();
this.trackChanges();
}else{
console.warn("Grouping Update - cant refresh view, no groups have been set");
}
}
setGroupHeader(values){
this.table.options.groupHeader = values;
this.configureGroupSetup();
if(this.table.options.groupBy){
this.refreshData();
this.trackChanges();
}else{
console.warn("Grouping Update - cant refresh view, no groups have been set");
}
}
userGetGroups(values){
return this.getGroups(true);
}
// get grouped table data in the same format as getData()
userGetGroupedData(){
return this.table.options.groupBy ?
this.getGroupedData() : this.getData();
}
///////////////////////////////////////
///////// Component Functions /////////
///////////////////////////////////////
rowGetGroup(row){
return row.modules.group ? row.modules.group.getComponent() : false;
}
///////////////////////////////////
///////// Internal Logic //////////
///////////////////////////////////
rowMoving(from, to, after){
if(!after && to instanceof Group){
to = this.table.rowManager.prevDisplayRow(from) || to;
}
var toGroup = to instanceof Group ? to : to.modules.group;
var fromGroup = from instanceof Group ? from : from.modules.group;
if(toGroup === fromGroup){
this.table.rowManager.moveRowInArray(toGroup.rows, from, to, after);
}else{
if(fromGroup){
fromGroup.removeRow(from);
}
toGroup.insertRow(from, to, after);
}
}
rowDeleting(row){
//remove from group
if(row.modules.group){
row.modules.group.removeRow(row);
}
}
rowsUpdated(row){
this.updateGroupRows(true);
}
cellUpdated(cell){
this.reassignRowToGroup(cell.row);
}
setDisplayIndex(index){
this.displayIndex = index;
}
getDisplayIndex(){
return this.displayIndex;
}
//return appropriate rows with group headers
getRows(rows){
if(this.table.options.groupBy && this.groupIDLookups.length){
this.dispatchExternal("dataGrouping");
this.generateGroups(rows);
if(this.subscribedExternal("dataGrouped")){
this.dispatchExternal("dataGrouped", this.getGroups(true));
}
return this.updateGroupRows();
}else{
return rows.slice(0);
}
}
getGroups(component){
var groupComponents = [];
this.groupList.forEach(function(group){
groupComponents.push(component ? group.getComponent() : group);
});
return groupComponents;
}
getChildGroups(group){
var groupComponents = [];
if(!group){
group = this;
}
group.groupList.forEach((child) => {
if(child.groupList.length){
groupComponents = groupComponents.concat(this.getChildGroups(child));
}else{
groupComponents.push(child);
}
});
return groupComponents;
}
wipe(){
this.groupList.forEach(function(group){
group.wipe();
});
}
pullGroupListData(groupList) {
var groupListData = [];
groupList.forEach((group) => {
var groupHeader = {};
groupHeader.level = 0;
groupHeader.rowCount = 0;
groupHeader.headerContent = "";
var childData = [];
if (group.hasSubGroups) {
childData = this.pullGroupListData(group.groupList);
groupHeader.level = group.level;
groupHeader.rowCount = childData.length - group.groupList.length; // data length minus number of sub-headers
groupHeader.headerContent = group.generator(group.key, groupHeader.rowCount, group.rows, group);
groupListData.push(groupHeader);
groupListData = groupListData.concat(childData);
}
else {
groupHeader.level = group.level;
groupHeader.headerContent = group.generator(group.key, group.rows.length, group.rows, group);
groupHeader.rowCount = group.getRows().length;
groupListData.push(groupHeader);
group.getRows().forEach((row) => {
groupListData.push(row.getData("data"));
});
}
});
return groupListData;
}
getGroupedData(){
return this.pullGroupListData(this.groupList);
}
getRowGroup(row){
var match = false;
if(this.options("dataTree")){
row = this.table.modules.dataTree.getTreeParentRoot(row);
}
this.groupList.forEach((group) => {
var result = group.getRowGroup(row);
if(result){
match = result;
}
});
return match;
}
countGroups(){
return this.groupList.length;
}
generateGroups(rows){
var oldGroups = this.groups;
this.groups = {};
this.groupList = [];
if(this.allowedValues && this.allowedValues[0]){
this.allowedValues[0].forEach((value) => {
this.createGroup(value, 0, oldGroups);
});
rows.forEach((row) => {
this.assignRowToExistingGroup(row, oldGroups);
});
}else{
rows.forEach((row) => {
this.assignRowToGroup(row, oldGroups);
});
}
}
createGroup(groupID, level, oldGroups){
var groupKey = level + "_" + groupID,
group;
oldGroups = oldGroups || [];
group = new Group(this, false, level, groupID, this.groupIDLookups[0].field, this.headerGenerator[0], oldGroups[groupKey]);
this.groups[groupKey] = group;
this.groupList.push(group);
}
assignRowToExistingGroup(row, oldGroups){
var groupID = this.groupIDLookups[0].func(row.getData()),
groupKey = "0_" + groupID;
if(this.groups[groupKey]){
this.groups[groupKey].addRow(row);
}
}
assignRowToGroup(row, oldGroups){
var groupID = this.groupIDLookups[0].func(row.getData()),
newGroupNeeded = !this.groups["0_" + groupID];
if(newGroupNeeded){
this.createGroup(groupID, 0, oldGroups);
}
this.groups["0_" + groupID].addRow(row);
return !newGroupNeeded;
}
reassignRowToGroup(row){
if(row.type === "row"){
var oldRowGroup = row.modules.group,
oldGroupPath = oldRowGroup.getPath(),
newGroupPath = this.getExpectedPath(row),
samePath;
// figure out if new group path is the same as old group path
samePath = (oldGroupPath.length == newGroupPath.length) && oldGroupPath.every((element, index) => {
return element === newGroupPath[index];
});
// refresh if they new path and old path aren't the same (aka the row's groupings have changed)
if(!samePath) {
oldRowGroup.removeRow(row);
this.assignRowToGroup(row, this.groups);
this.refreshData(true);
}
}
}
getExpectedPath(row) {
var groupPath = [], rowData = row.getData();
this.groupIDLookups.forEach((groupId) => {
groupPath.push(groupId.func(rowData));
});
return groupPath;
}
updateGroupRows(force){
var output = [];
this.groupList.forEach((group) => {
output = output.concat(group.getHeadersAndRows());
});
if(force){
this.refreshData(true, this.displayHandler);
}
return output;
}
scrollHeaders(left){
if(this.table.options.renderHorizontal === "virtual"){
left -= this.table.columnManager.renderer.vDomPadLeft;
}
left = left + "px";
this.groupList.forEach((group) => {
group.scrollHeader(left);
});
}
removeGroup(group){
var groupKey = group.level + "_" + group.key,
index;
if(this.groups[groupKey]){
delete this.groups[groupKey];
index = this.groupList.indexOf(group);
if(index > -1){
this.groupList.splice(index, 1);
}
}
}
checkBasicModeGroupHeaderWidth(){
var element = this.table.rowManager.tableElement,
onlyGroupHeaders = true;
this.table.rowManager.getDisplayRows().forEach((row, index) =>{
this.table.rowManager.styleRow(row, index);
element.appendChild(row.getElement());
row.initialize(true);
if(row.type !== "group"){
onlyGroupHeaders = false;
}
});
if(onlyGroupHeaders){
element.style.minWidth = this.table.columnManager.getWidth() + "px";
}else{
element.style.minWidth = "";
}
}
}
GroupRows.moduleName = "groupRows";
export default GroupRows;