diff --git a/examples/Router/ExamplesGrid.js b/examples/Router/ExamplesGrid.js
index 73c23db5d..9ed2ad8c8 100644
--- a/examples/Router/ExamplesGrid.js
+++ b/examples/Router/ExamplesGrid.js
@@ -54,7 +54,6 @@ class ExamplesGrid extends React.Component {
const examplesSortedKeys = Object.keys(examplesSorted).filter((item) => {
if (this.state.searchVal === '') return true;
- console.dir(item);
return item.toLowerCase().indexOf( this.state.searchVal.toLowerCase() ) !== -1 ? true : false;
});
diff --git a/examples/examples.js b/examples/examples.js
index 877af6cc4..5535ba3bf 100644
--- a/examples/examples.js
+++ b/examples/examples.js
@@ -34,6 +34,7 @@ import CustomComponents from './custom-components';
import InfiniteScrolling from './infinite-scrolling';
import Themes from './themes';
import LargeDataSet from './large-data-set';
+import Grouping from './grouping';
/**
* Here you can add any extra examples with the Card label as the key, and the component to render as the value
@@ -60,6 +61,7 @@ export default {
'Draggable Columns': DraggableColumns,
'Expandable Rows': ExpandableRows,
'Fixed Header': FixedHeader,
+ 'Grouping': Grouping,
'Hide Columns Print': HideColumnsPrint,
'Infinite Scrolling': InfiniteScrolling,
'Large Data Set': LargeDataSet,
diff --git a/examples/grouping/index.js b/examples/grouping/index.js
new file mode 100644
index 000000000..f5712d4d1
--- /dev/null
+++ b/examples/grouping/index.js
@@ -0,0 +1,125 @@
+import React, { useState } from 'react';
+import ReactDOM from 'react-dom';
+import MUIDataTable from '../../src/';
+import InputLabel from '@material-ui/core/InputLabel';
+import MenuItem from '@material-ui/core/MenuItem';
+import FormHelperText from '@material-ui/core/FormHelperText';
+import FormControl from '@material-ui/core/FormControl';
+import Select from '@material-ui/core/Select';
+
+function Example() {
+ const [responsive, setResponsive] = useState('vertical');
+ const [tableBodyHeight, setTableBodyHeight] = useState('400px');
+ const [tableBodyMaxHeight, setTableBodyMaxHeight] = useState('');
+
+ const columns = [
+ {
+ name: 'name',
+ label: 'Name',
+ options: {
+ setCellHeaderProps: () => ({
+ style: {
+ width: '25%'
+ }
+ }),
+ }
+ },
+ {
+ name: 'title',
+ label: 'Title',
+ options: {
+ setCellHeaderProps: () => ({
+ style: {
+ width: '25%'
+ }
+ }),
+ }
+ },
+ {
+ name: 'location',
+ label: 'Location',
+ options: {
+ setCellHeaderProps: () => ({
+ style: {
+ width: '25%'
+ }
+ }),
+ }
+ },
+ {
+ name: 'gender',
+ label: 'Gender'
+ }
+ ];
+
+ const options = {
+ filter: true,
+ filterType: 'dropdown',
+ responsive,
+ pagination: false,
+ draggableColumns: {
+ enabled: true,
+ },
+ onTableChange: (action, state) => {
+ console.log(action);
+ //console.dir(state);
+ },
+ onGroupExpansionChange: (group, expanded) => {
+ console.dir(group);
+ console.dir(expanded);
+ },
+ grouping: {
+ columnIndexes: [1, 3],
+ expanded: {
+ "Business Consultant": {
+ open: true
+ }
+ }
+ }
+ };
+
+ const data = [
+ ['Gabby George', 'Business Analyst', 'Minneapolis', 'female'],
+ ['Aiden Lloyd', "Business Consultant", 'Dallas', 'male'],
+ ['Jaden Collins', 'Attorney', 'Santa Ana', 'male'],
+ ['Franky Rees', 'Business Analyst', 'St. Petersburg', 'male'],
+ ['Aaren Rose', null, 'Toledo', 'male'],
+ ['Johnny Jones', 'Business Analyst', 'St. Petersburg', 'male'],
+ ['Jimmy Johns', 'Business Analyst', 'Baltimore', 'male'],
+ ['Jack Jackson', 'Business Analyst', 'El Paso', 'male'],
+ ['Joe Jones', 'Computer Programmer', 'El Paso', 'male'],
+ ['Jacky Jackson', 'Business Consultant', 'Baltimore', 'female'],
+ ['Jo Jo', 'Software Developer', 'Washington DC', 'male'],
+ ['Donna Marie', 'Business Manager', 'Annapolis', 'female'],
+ ['Armin Tamzarian', 'Principal', 'Springfield', 'male'],
+ ['Gerald Strickland', 'Principal', 'Hill Valley', 'male'],
+ ['Doc Brown', 'Computer Programmer', 'Hill Valley', 'male'],
+ ['Angela Li', 'Principal', 'Lawndale', 'female'],
+ ['Jake Morgendorffer', 'Business Analyst', 'Lawndale', 'male'],
+ ];
+
+ return (
+
+
+ Responsive Option
+
+
+
+
+ );
+}
+
+export default Example;
diff --git a/package-lock.json b/package-lock.json
index 3e8e9e57b..6dfb82a6a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "mui-datatables",
- "version": "3.4.1",
+ "version": "3.5.0-beta.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -4080,7 +4080,8 @@
},
"kind-of": {
"version": "6.0.2",
- "resolved": ""
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
}
}
},
@@ -15598,7 +15599,8 @@
},
"kind-of": {
"version": "6.0.2",
- "resolved": ""
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
}
}
},
diff --git a/package.json b/package.json
index 3e9685751..f7619ae8c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mui-datatables",
- "version": "3.4.1",
+ "version": "3.5.0-beta.0",
"description": "Datatables for React using Material-UI",
"main": "dist/index.js",
"files": [
diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js
index 9abe8a45c..6b562f78a 100644
--- a/src/MUIDataTable.js
+++ b/src/MUIDataTable.js
@@ -11,6 +11,7 @@ import merge from 'lodash.merge';
import PropTypes from 'prop-types';
import React from 'react';
import DefaultTableBody from './components/TableBody';
+import TableBodyGroups from './components/TableBodyGroups';
import DefaultTableFilter from './components/TableFilter';
import DefaultTableFilterList from './components/TableFilterList';
import DefaultTableFooter from './components/TableFooter';
@@ -194,6 +195,7 @@ class MUIDataTable extends React.Component {
fixedHeader: PropTypes.bool,
fixedSelectColumn: PropTypes.bool,
getTextLabels: PropTypes.func,
+ grouping: PropTypes.object,
isRowExpandable: PropTypes.func,
isRowSelectable: PropTypes.func,
jumpToPage: PropTypes.bool,
@@ -289,6 +291,7 @@ class MUIDataTable extends React.Component {
},
data: [],
displayData: [],
+ groupingData: {},
filterData: [],
filterList: [],
page: 0,
@@ -395,6 +398,7 @@ class MUIDataTable extends React.Component {
filterType: 'dropdown',
fixedHeader: true,
fixedSelectColumn: true,
+ grouping: null,
pagination: true,
print: true,
resizableColumns: false,
@@ -896,7 +900,26 @@ class MUIDataTable extends React.Component {
tableData = sortedData.data;
}
+ let grouping = {};
+ if (this.options.grouping) {
+ if (this.options.grouping.columnIndexes) {
+ grouping.columnIndexes = this.options.grouping.columnIndexes;
+ } else if (this.state.grouping && this.state.grouping.columnIndexes) {
+ grouping.columnIndexes = this.state.grouping.columnIndexes;
+ }
+ if (this.options.grouping.expanded) {
+ grouping.expanded = this.options.grouping.expanded;
+ } else if (this.state.grouping && this.state.grouping.expanded) {
+ grouping.expanded = this.state.grouping.expanded;
+ } else {
+ grouping.expanded = {};
+ }
+ } else if (this.state.grouping) {
+ grouping = this.state.grouping;
+ }
+
/* set source data and display Data set source set */
+ let nextDisplayData = this.getDisplayData(columns, tableData, filterList, searchText, tableMeta, props);
let stateUpdates = {
columns: columns,
filterData: filterData,
@@ -907,9 +930,11 @@ class MUIDataTable extends React.Component {
count: this.options.count,
data: tableData,
sortOrder: sortOrder,
+ displayData: nextDisplayData,
+ groupingData: this.getGroupingData(nextDisplayData, grouping),
+ grouping: grouping,
rowsPerPage,
page,
- displayData: this.getDisplayData(columns, tableData, filterList, searchText, tableMeta, props),
columnOrder,
};
@@ -1085,17 +1110,20 @@ class MUIDataTable extends React.Component {
filterData[index].sort(comparator);
}
+ let nextDisplayData = this.getDisplayData(
+ prevState.columns,
+ changedData,
+ prevState.filterList,
+ prevState.searchText,
+ null,
+ this.props,
+ );
+
return {
data: changedData,
filterData: filterData,
- displayData: this.getDisplayData(
- prevState.columns,
- changedData,
- prevState.filterList,
- prevState.searchText,
- null,
- this.props,
- ),
+ displayData: nextDisplayData,
+ groupingData: this.getGroupingData(nextDisplayData, this.state.grouping),
};
});
};
@@ -1114,6 +1142,105 @@ class MUIDataTable extends React.Component {
};
};
+ isGroupExpanded(grouping, group) {
+ let expanded = grouping.expanded || {};
+ let isExpanded = true;
+
+ for (let ii = 0; ii < group.length; ii++) {
+ expanded = expanded[group[ii]];
+ if (!expanded) {
+ isExpanded = false;
+ break;
+ }
+ }
+ isExpanded = isExpanded && expanded.open;
+
+ return isExpanded;
+ }
+
+ toggleGroupExpansion(group) {
+ let expanded = cloneDeep(this.state.grouping.expanded || {});
+ let exp = expanded;
+
+ for (let ii = 0; ii < group.length; ii++) {
+ exp[group[ii]] = exp[group[ii]] || {};
+ exp = exp[group[ii]];
+ }
+ exp.open = !exp.open;
+
+ let grouping = cloneDeep(this.state.grouping);
+ grouping.expanded = expanded;
+
+ this.setState(
+ prevState => ({
+ grouping: grouping,
+ }),
+ () => {
+ this.setTableAction('groupingExpansionChange');
+ var cb = this.options.onGroupExpansionChange;
+ if (cb) {
+ cb(group, expanded);
+ }
+ },
+ );
+ }
+
+ getGroups(grouping, colIndexes, data, level, group) {
+ let map = {};
+ let colIndex = colIndexes[0];
+ data.forEach(row => {
+ map[row.data[colIndex]] = map[row.data[colIndex]] || [];
+ map[row.data[colIndex]].push(row);
+ });
+
+ if (colIndexes.length > 1) {
+ for (let prop in map) {
+ let nextGroup = group.slice();
+ nextGroup.push(prop);
+ map[prop] = this.getGroups(grouping, colIndexes.slice(1), map[prop], level + 1, nextGroup);
+ }
+ } else {
+ for (let prop in map) {
+ let nextGroup = group.slice();
+ nextGroup.push(prop);
+ map[prop] = {
+ level: level,
+ group: nextGroup,
+ onExpansionChange: () => {
+ this.toggleGroupExpansion(nextGroup);
+ },
+ data: map[prop],
+ };
+ }
+ }
+
+ return {
+ groupColumnIndex: colIndex,
+ level: level,
+ groups: map,
+ onExpansionChange: () => {
+ this.toggleGroupExpansion(group);
+ },
+ group: group,
+ };
+ }
+
+ getGroupingData(displayData, grouping) {
+ if (!grouping) return null;
+
+ //console.log('getGroupingData');
+ //console.log(grouping);
+
+ let cols = grouping.columnIndexes;
+
+ if (!cols || cols.length === 0) return null;
+
+ //console.dir(displayData);
+ let groups = this.getGroups(grouping, cols, displayData, 1, []);
+ //console.dir(groups);
+ return groups;
+ }
+
getDisplayData(columns, data, filterList, searchText, tableMeta, props) {
let newRows = [];
const dataForTableMeta = tableMeta ? tableMeta.tableData : props.data;
@@ -1251,17 +1378,20 @@ class MUIDataTable extends React.Component {
} else {
const sortedData = this.sortTable(data, index, newOrder, columns[index].sortCompare);
+ let nextDisplayData = this.getDisplayData(
+ columns,
+ sortedData.data,
+ prevState.filterList,
+ prevState.searchText,
+ null,
+ this.props,
+ );
+
newState = {
...newState,
data: sortedData.data,
- displayData: this.getDisplayData(
- columns,
- sortedData.data,
- prevState.filterList,
- prevState.searchText,
- null,
- this.props,
- ),
+ displayData: nextDisplayData,
+ groupingData: this.getGroupingData(nextDisplayData, this.state.grouping),
selectedRows: sortedData.selectedRows,
sortOrder: newSortOrder,
previousSelectedRow: null,
@@ -1314,12 +1444,17 @@ class MUIDataTable extends React.Component {
searchClose = () => {
this.setState(
- prevState => ({
- searchText: null,
- displayData: this.options.serverSide
+ prevState => {
+ let nextDisplayData = this.options.serverSide
? prevState.displayData
- : this.getDisplayData(prevState.columns, prevState.data, prevState.filterList, null, null, this.props),
- }),
+ : this.getDisplayData(prevState.columns, prevState.data, prevState.filterList, null, null, this.props);
+
+ return {
+ searchText: null,
+ displayData: nextDisplayData,
+ groupingData: this.getGroupingData(nextDisplayData, this.state.grouping),
+ };
+ },
() => {
this.setTableAction('search');
if (this.options.onSearchChange) {
@@ -1331,13 +1466,18 @@ class MUIDataTable extends React.Component {
searchTextUpdate = text => {
this.setState(
- prevState => ({
- searchText: text && text.length ? text : null,
- page: 0,
- displayData: this.options.serverSide
+ prevState => {
+ let nextDisplayData = this.options.serverSide
? prevState.displayData
- : this.getDisplayData(prevState.columns, prevState.data, prevState.filterList, text, null, this.props),
- }),
+ : this.getDisplayData(prevState.columns, prevState.data, prevState.filterList, text, null, this.props);
+
+ return {
+ searchText: text && text.length ? text : null,
+ page: 0,
+ displayData: nextDisplayData,
+ groupingData: this.getGroupingData(nextDisplayData, this.state.grouping),
+ };
+ },
() => {
this.setTableAction('search');
if (this.options.onSearchChange) {
@@ -1352,18 +1492,14 @@ class MUIDataTable extends React.Component {
prevState => {
const filterList = prevState.columns.map(() => []);
+ let nextDisplayData = this.options.serverSide
+ ? prevState.displayData
+ : this.getDisplayData(prevState.columns, prevState.data, filterList, prevState.searchText, null, this.props);
+
return {
filterList: filterList,
- displayData: this.options.serverSide
- ? prevState.displayData
- : this.getDisplayData(
- prevState.columns,
- prevState.data,
- filterList,
- prevState.searchText,
- null,
- this.props,
- ),
+ displayData: nextDisplayData,
+ groupingData: this.getGroupingData(nextDisplayData, this.state.grouping),
};
},
() => {
@@ -1409,19 +1545,15 @@ class MUIDataTable extends React.Component {
const filterList = cloneDeep(prevState.filterList);
this.updateFilterByType(filterList, index, value, type, customUpdate);
+ let nextDisplayData = this.options.serverSide
+ ? prevState.displayData
+ : this.getDisplayData(prevState.columns, prevState.data, filterList, prevState.searchText, null, this.props);
+
return {
page: 0,
filterList: filterList,
- displayData: this.options.serverSide
- ? prevState.displayData
- : this.getDisplayData(
- prevState.columns,
- prevState.data,
- filterList,
- prevState.searchText,
- null,
- this.props,
- ),
+ displayData: nextDisplayData,
+ groupingData: this.getGroupingData(nextDisplayData, this.state.grouping),
previousSelectedRow: null,
};
},
@@ -1823,7 +1955,7 @@ class MUIDataTable extends React.Component {
columnOrder,
} = this.state;
- const TableBodyComponent = TableBody || DefaultTableBody;
+ const TableBodyComponent = this.state.groupingData ? TableBodyGroups : TableBody || DefaultTableBody;
const TableFilterListComponent = TableFilterList || DefaultTableFilterList;
const TableFooterComponent = TableFooter || DefaultTableFooter;
const TableHeadComponent = TableHead || DefaultTableHead;
@@ -2000,6 +2132,9 @@ class MUIDataTable extends React.Component {
/>
{
- let shiftKey = event && event.nativeEvent ? event.nativeEvent.shiftKey : false;
- let shiftAdjacentRows = [];
- let previousSelectedRow = this.props.previousSelectedRow;
-
- // If the user is pressing shift and has previously clicked another row.
- if (shiftKey && previousSelectedRow && previousSelectedRow.index < this.props.data.length) {
- let curIndex = previousSelectedRow.index;
-
- // Create a copy of the selectedRows object. This will be used and modified
- // below when we see if we can add adjacent rows.
- let selectedRows = cloneDeep(this.props.selectedRows);
-
- // Add the clicked on row to our copy of selectedRows (if it isn't already present).
- let clickedDataIndex = this.props.data[data.index].dataIndex;
- if (selectedRows.data.filter(d => d.dataIndex === clickedDataIndex).length === 0) {
- selectedRows.data.push({
- index: data.index,
- dataIndex: clickedDataIndex,
- });
- selectedRows.lookup[clickedDataIndex] = true;
- }
-
- while (curIndex !== data.index) {
- let dataIndex = this.props.data[curIndex].dataIndex;
-
- if (this.isRowSelectable(dataIndex, selectedRows)) {
- let lookup = {
- index: curIndex,
- dataIndex: dataIndex,
- };
-
- // Add adjacent row to temp selectedRow object if it isn't present.
- if (selectedRows.data.filter(d => d.dataIndex === dataIndex).length === 0) {
- selectedRows.data.push(lookup);
- selectedRows.lookup[dataIndex] = true;
- }
-
- shiftAdjacentRows.push(lookup);
- }
- curIndex = data.index > curIndex ? curIndex + 1 : curIndex - 1;
- }
- }
- this.props.selectRowUpdate('cell', data, shiftAdjacentRows);
- };
-
- handleRowClick = (row, data, event) => {
- // Don't trigger onRowClick if the event was actually the expandable icon.
- if (
- event.target.id === 'expandable-button' ||
- (event.target.nodeName === 'path' && event.target.parentNode.id === 'expandable-button')
- ) {
- return;
- }
-
- // Don't trigger onRowClick if the event was actually a row selection via checkbox
- if (event.target.id && event.target.id.startsWith('MUIDataTableSelectCell')) return;
-
- // Check if we should toggle row select when row is clicked anywhere
- if (
- this.props.options.selectableRowsOnClick &&
- this.props.options.selectableRows !== 'none' &&
- this.isRowSelectable(data.dataIndex, this.props.selectedRows)
- ) {
- const selectRow = { index: data.rowIndex, dataIndex: data.dataIndex };
- this.handleRowSelect(selectRow, event);
- }
- // Check if we should trigger row expand when row is clicked anywhere
- if (
- this.props.options.expandableRowsOnClick &&
- this.props.options.expandableRows &&
- this.isRowExpandable(data.dataIndex, this.props.expandedRows)
- ) {
- const expandRow = { index: data.rowIndex, dataIndex: data.dataIndex };
- this.props.toggleExpandRow(expandRow);
- }
-
- // Don't trigger onRowClick if the event was actually a row selection via click
- if (this.props.options.selectableRowsOnClick) return;
-
- this.props.options.onRowClick && this.props.options.onRowClick(row, data, event);
- };
-
- processRow = (row, columnOrder) => {
- let ret = [];
- for (let ii = 0; ii < row.length; ii++) {
- ret.push({
- value: row[columnOrder[ii]],
- index: columnOrder[ii],
- });
- }
- return ret;
- };
-
render() {
const {
classes,
@@ -232,104 +98,27 @@ class TableBody extends React.Component {
tableId,
} = this.props;
const tableRows = this.buildRows();
- const visibleColCnt = columns.filter(c => c.display === 'true').length;
return (
- {tableRows && tableRows.length > 0 ? (
- tableRows.map((data, rowIndex) => {
- const { data: row, dataIndex } = data;
-
- if (options.customRowRender) {
- return options.customRowRender(row, dataIndex, rowIndex);
- }
-
- let isRowSelected = options.selectableRows !== 'none' ? this.isRowSelected(dataIndex) : false;
- let isRowSelectable = this.isRowSelectable(dataIndex);
- let bodyClasses = options.setRowProps ? options.setRowProps(row, dataIndex, rowIndex) || {} : {};
-
- const processedRow = this.processRow(row, columnOrder);
-
- return (
-
-
-
- {processedRow.map(
- column =>
- columns[column.index].display === 'true' && (
-
- {column.value}
-
- ),
- )}
-
- {this.isRowExpanded(dataIndex) && options.renderExpandableRow(row, { rowIndex, dataIndex })}
-
- );
- })
- ) : (
-
-
-
- {options.textLabels.body.noMatch}
-
-
-
- )}
+
);
}
diff --git a/src/components/TableBodyGroupDataRow.js b/src/components/TableBodyGroupDataRow.js
new file mode 100644
index 000000000..8ae14015e
--- /dev/null
+++ b/src/components/TableBodyGroupDataRow.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import TableBodyRows from './TableBodyRows';
+import { withStyles } from '@material-ui/core/styles';
+import cloneDeep from 'lodash.clonedeep';
+import { getPageValue } from '../utils';
+import clsx from 'clsx';
+
+const defaultBodyStyles = theme => ({
+ root: {},
+});
+
+function TableBodyGroupDataRow(props) {
+ const { row } = props;
+ console.dir(props);
+ return (
+
+ );
+}
+
+export default TableBodyGroupDataRow;
diff --git a/src/components/TableBodyGroupHeaderRow.js b/src/components/TableBodyGroupHeaderRow.js
new file mode 100644
index 000000000..4649a607f
--- /dev/null
+++ b/src/components/TableBodyGroupHeaderRow.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Typography from '@material-ui/core/Typography';
+import TableBodyCell from './TableBodyCell';
+import TableRow from '@material-ui/core/TableRow';
+import TableCell from '@material-ui/core/TableCell';
+import TableSelectCell from './TableSelectCell';
+import { makeStyles } from '@material-ui/core/styles';
+import clsx from 'clsx';
+import IconButton from '@material-ui/core/IconButton';
+import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight';
+
+const useStyles = makeStyles(
+ theme => ({
+ root: {},
+ columnName: {
+ fontWeight: 'bold',
+ display: 'inline-block',
+ },
+ columnValue: {
+ marginLeft: '6px',
+ display: 'inline-block',
+ },
+ icon: {
+ cursor: 'pointer',
+ transition: 'transform 0.25s',
+ },
+ expanded: {
+ transform: 'rotate(90deg)',
+ },
+ tableRow: {
+ padding: '4px 8px 8px 4px',
+ },
+ expandButton: {
+ marginRight: '10px',
+ },
+ }),
+ { name: 'MUIDataTableBodyGroupHeaderRow' },
+);
+
+function TableBodyGroupHeaderRow(props) {
+ const { columns, options, components = {}, tableId, row } = props;
+ const classes = useStyles();
+
+ const onExpand = () => {};
+
+ const iconClass = clsx({
+ [classes.icon]: true,
+ [classes.expanded]: row.expanded,
+ });
+
+ const getLevelOffset = level => {
+ return (level - 1) * 32 + 'px';
+ };
+
+ let bodyClasses = options.setRowProps ? options.setRowProps(row, null, null) || {} : {};
+ //console.dir(row);
+ return (
+
+
+
+
+
+ {row.columnLabel}:
+ {row.columnValue}
+
+
+ );
+}
+
+export default TableBodyGroupHeaderRow;
diff --git a/src/components/TableBodyGroups.js b/src/components/TableBodyGroups.js
new file mode 100644
index 000000000..fd4b3a662
--- /dev/null
+++ b/src/components/TableBodyGroups.js
@@ -0,0 +1,145 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Typography from '@material-ui/core/Typography';
+import MuiTableBody from '@material-ui/core/TableBody';
+import TableBodyCell from './TableBodyCell';
+import TableBodyRow from './TableBodyRow';
+import TableSelectCell from './TableSelectCell';
+import TableBodyGroupHeaderRow from './TableBodyGroupHeaderRow';
+import TableBodyGroupDataRow from './TableBodyGroupDataRow';
+import { withStyles } from '@material-ui/core/styles';
+import cloneDeep from 'lodash.clonedeep';
+import { getPageValue } from '../utils';
+import clsx from 'clsx';
+
+const defaultBodyStyles = theme => ({
+ root: {},
+});
+
+class TableBody extends React.Component {
+ static propTypes = {
+ /** Data used to describe table */
+ data: PropTypes.array.isRequired,
+ /** Total number of data rows */
+ count: PropTypes.number.isRequired,
+ /** Columns used to describe table */
+ columns: PropTypes.array.isRequired,
+ /** Options used to describe table */
+ options: PropTypes.object.isRequired,
+ /** Data used to filter table against */
+ filterList: PropTypes.array,
+ /** Callback to execute when row is clicked */
+ onRowClick: PropTypes.func,
+ /** Table rows expanded */
+ expandedRows: PropTypes.object,
+ /** Table rows selected */
+ selectedRows: PropTypes.object,
+ /** Callback to trigger table row select */
+ selectRowUpdate: PropTypes.func,
+ /** The most recent row to have been selected/unselected */
+ previousSelectedRow: PropTypes.object,
+ /** Data used to search table against */
+ searchText: PropTypes.string,
+ /** Toggle row expandable */
+ toggleExpandRow: PropTypes.func,
+ /** Extend the style applied to components */
+ classes: PropTypes.object,
+ };
+
+ static defaultProps = {
+ toggleExpandRow: () => {},
+ };
+
+ flattenGroups(rows, rootGroup, columns, grouping, isGroupExpanded) {
+ for (let prop in rootGroup.groups) {
+ let group = rootGroup.groups[prop];
+ if (group.data) {
+ let isExpanded = isGroupExpanded(grouping, group.group);
+ rows.push({
+ rowType: 'group',
+ level: rootGroup.level,
+ id: group.group.join('___GROUPJOIN___'),
+ columnIndex: rootGroup.groupColumnIndex,
+ columnName: columns[rootGroup.groupColumnIndex].name,
+ columnLabel: columns[rootGroup.groupColumnIndex].label || columns[rootGroup.groupColumnIndex].name,
+ columnValue: prop,
+ expanded: isExpanded,
+ onExpansionChange: group.onExpansionChange,
+ });
+
+ if (isExpanded) {
+ rows.push({
+ rowType: 'data',
+ id: group.group.join('___GROUPJOIN___') + '_data',
+ data: group,
+ });
+ }
+ } else {
+ let isExpanded = isGroupExpanded(grouping, group.group);
+ rows.push({
+ rowType: 'group',
+ level: rootGroup.level,
+ id: group.group.join('___GROUPJOIN___'),
+ columnIndex: rootGroup.groupColumnIndex,
+ columnName: columns[rootGroup.groupColumnIndex].name,
+ columnLabel: columns[rootGroup.groupColumnIndex].label || columns[rootGroup.groupColumnIndex].name,
+ columnValue: prop,
+ expanded: isExpanded,
+ onExpansionChange: group.onExpansionChange,
+ });
+
+ if (isExpanded) {
+ this.flattenGroups(rows, rootGroup.groups[prop], columns, grouping, isGroupExpanded);
+ }
+ }
+ }
+
+ return rows;
+ }
+
+ buildRows(groupingData, columns, grouping, isGroupExpanded) {
+ let rows = this.flattenGroups([], groupingData, columns, grouping, isGroupExpanded);
+ return rows;
+ }
+
+ render() {
+ const { grouping, isGroupExpanded, classes, columns, groupingData } = this.props;
+ let tableData = this.props.data;
+
+ let rows = this.buildRows(groupingData, columns, grouping, isGroupExpanded);
+
+ console.log('rows');
+ console.dir(rows);
+
+ return (
+
+ {rows.map((data, rowIndex) => {
+ let RowComponent = data.rowType === 'group' ? TableBodyGroupHeaderRow : TableBodyGroupDataRow;
+ return (
+
+ );
+ })}
+
+ );
+ }
+}
+
+export default withStyles(defaultBodyStyles, { name: 'MUIDataTableBody' })(TableBody);
diff --git a/src/components/TableBodyRows.js b/src/components/TableBodyRows.js
new file mode 100644
index 000000000..281ecc9c0
--- /dev/null
+++ b/src/components/TableBodyRows.js
@@ -0,0 +1,317 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Typography from '@material-ui/core/Typography';
+import MuiTableBody from '@material-ui/core/TableBody';
+import TableBodyCell from './TableBodyCell';
+import TableBodyRow from './TableBodyRow';
+import TableSelectCell from './TableSelectCell';
+import { withStyles } from '@material-ui/core/styles';
+import cloneDeep from 'lodash.clonedeep';
+import { getPageValue } from '../utils';
+import clsx from 'clsx';
+
+const defaultBodyStyles = theme => ({
+ root: {},
+ emptyTitle: {
+ textAlign: 'center',
+ },
+ lastStackedCell: {
+ [theme.breakpoints.down('sm')]: {
+ '& td:last-child': {
+ borderBottom: 'none',
+ },
+ },
+ },
+ lastSimpleCell: {
+ [theme.breakpoints.down('xs')]: {
+ '& td:last-child': {
+ borderBottom: 'none',
+ },
+ },
+ },
+});
+
+class TableBodyRows extends React.Component {
+ static propTypes = {
+ /** Data used to describe table */
+ data: PropTypes.array.isRequired,
+ /** Total number of data rows */
+ count: PropTypes.number.isRequired,
+ /** Columns used to describe table */
+ columns: PropTypes.array.isRequired,
+ /** Options used to describe table */
+ options: PropTypes.object.isRequired,
+ /** Data used to filter table against */
+ filterList: PropTypes.array,
+ /** Callback to execute when row is clicked */
+ onRowClick: PropTypes.func,
+ /** Table rows expanded */
+ expandedRows: PropTypes.object,
+ /** Table rows selected */
+ selectedRows: PropTypes.object,
+ /** Callback to trigger table row select */
+ selectRowUpdate: PropTypes.func,
+ /** The most recent row to have been selected/unselected */
+ previousSelectedRow: PropTypes.object,
+ /** Data used to search table against */
+ searchText: PropTypes.string,
+ /** Toggle row expandable */
+ toggleExpandRow: PropTypes.func,
+ /** Extend the style applied to components */
+ classes: PropTypes.object,
+ };
+
+ static defaultProps = {
+ toggleExpandRow: () => {},
+ };
+
+ getRowIndex(index) {
+ const { page, rowsPerPage, options } = this.props;
+
+ if (options.serverSide) {
+ return index;
+ }
+
+ const startIndex = page === 0 ? 0 : page * rowsPerPage;
+ return startIndex + index;
+ }
+
+ isRowSelected(dataIndex) {
+ const { selectedRows } = this.props;
+ return selectedRows.lookup && selectedRows.lookup[dataIndex] ? true : false;
+ }
+
+ isRowExpanded(dataIndex) {
+ const { expandedRows } = this.props;
+ return expandedRows.lookup && expandedRows.lookup[dataIndex] ? true : false;
+ }
+
+ isRowSelectable(dataIndex, selectedRows) {
+ const { options } = this.props;
+ selectedRows = selectedRows || this.props.selectedRows;
+
+ if (options.isRowSelectable) {
+ return options.isRowSelectable(dataIndex, selectedRows);
+ } else {
+ return true;
+ }
+ }
+
+ isRowExpandable(dataIndex) {
+ const { options, expandedRows } = this.props;
+ if (options.isRowExpandable) {
+ return options.isRowExpandable(dataIndex, expandedRows);
+ } else {
+ return true;
+ }
+ }
+
+ handleRowSelect = (data, event) => {
+ let shiftKey = event && event.nativeEvent ? event.nativeEvent.shiftKey : false;
+ let shiftAdjacentRows = [];
+ let previousSelectedRow = this.props.previousSelectedRow;
+
+ // If the user is pressing shift and has previously clicked another row.
+ if (shiftKey && previousSelectedRow && previousSelectedRow.index < this.props.data.length) {
+ let curIndex = previousSelectedRow.index;
+
+ // Create a copy of the selectedRows object. This will be used and modified
+ // below when we see if we can add adjacent rows.
+ let selectedRows = cloneDeep(this.props.selectedRows);
+
+ // Add the clicked on row to our copy of selectedRows (if it isn't already present).
+ let clickedDataIndex = this.props.data[data.index].dataIndex;
+ if (selectedRows.data.filter(d => d.dataIndex === clickedDataIndex).length === 0) {
+ selectedRows.data.push({
+ index: data.index,
+ dataIndex: clickedDataIndex,
+ });
+ selectedRows.lookup[clickedDataIndex] = true;
+ }
+
+ while (curIndex !== data.index) {
+ let dataIndex = this.props.data[curIndex].dataIndex;
+
+ if (this.isRowSelectable(dataIndex, selectedRows)) {
+ let lookup = {
+ index: curIndex,
+ dataIndex: dataIndex,
+ };
+
+ // Add adjacent row to temp selectedRow object if it isn't present.
+ if (selectedRows.data.filter(d => d.dataIndex === dataIndex).length === 0) {
+ selectedRows.data.push(lookup);
+ selectedRows.lookup[dataIndex] = true;
+ }
+
+ shiftAdjacentRows.push(lookup);
+ }
+ curIndex = data.index > curIndex ? curIndex + 1 : curIndex - 1;
+ }
+ }
+ this.props.selectRowUpdate('cell', data, shiftAdjacentRows);
+ };
+
+ handleRowClick = (row, data, event) => {
+ // Don't trigger onRowClick if the event was actually the expandable icon.
+ if (
+ event.target.id === 'expandable-button' ||
+ (event.target.nodeName === 'path' && event.target.parentNode.id === 'expandable-button')
+ ) {
+ return;
+ }
+
+ // Don't trigger onRowClick if the event was actually a row selection via checkbox
+ if (event.target.id && event.target.id.startsWith('MUIDataTableSelectCell')) return;
+
+ // Check if we should toggle row select when row is clicked anywhere
+ if (
+ this.props.options.selectableRowsOnClick &&
+ this.props.options.selectableRows !== 'none' &&
+ this.isRowSelectable(data.dataIndex, this.props.selectedRows)
+ ) {
+ const selectRow = { index: data.rowIndex, dataIndex: data.dataIndex };
+ this.handleRowSelect(selectRow, event);
+ }
+ // Check if we should trigger row expand when row is clicked anywhere
+ if (
+ this.props.options.expandableRowsOnClick &&
+ this.props.options.expandableRows &&
+ this.isRowExpandable(data.dataIndex, this.props.expandedRows)
+ ) {
+ const expandRow = { index: data.rowIndex, dataIndex: data.dataIndex };
+ this.props.toggleExpandRow(expandRow);
+ }
+
+ // Don't trigger onRowClick if the event was actually a row selection via click
+ if (this.props.options.selectableRowsOnClick) return;
+
+ this.props.options.onRowClick && this.props.options.onRowClick(row, data, event);
+ };
+
+ processRow = (row, columnOrder) => {
+ let ret = [];
+ for (let ii = 0; ii < row.length; ii++) {
+ ret.push({
+ value: row[columnOrder[ii]],
+ index: columnOrder[ii],
+ });
+ }
+ return ret;
+ };
+
+ render() {
+ const {
+ classes,
+ columns,
+ toggleExpandRow,
+ options,
+ columnOrder = this.props.columns.map((item, idx) => idx),
+ components = {},
+ tableId,
+ tableRows,
+ } = this.props;
+ const visibleColCnt = columns.filter(c => c.display === 'true').length;
+
+ return (
+
+ {tableRows && tableRows.length > 0 ? (
+ tableRows.map((data, rowIndex) => {
+ const { data: row, dataIndex } = data;
+
+ if (options.customRowRender) {
+ return options.customRowRender(row, dataIndex, rowIndex);
+ }
+
+ let isRowSelected = options.selectableRows !== 'none' ? this.isRowSelected(dataIndex) : false;
+ let isRowSelectable = this.isRowSelectable(dataIndex);
+ let bodyClasses = options.setRowProps ? options.setRowProps(row, dataIndex, rowIndex) || {} : {};
+
+ const processedRow = this.processRow(row, columnOrder);
+
+ return (
+
+
+
+ {processedRow.map(
+ column =>
+ columns[column.index].display === 'true' && (
+
+ {column.value}
+
+ ),
+ )}
+
+ {this.isRowExpanded(dataIndex) && options.renderExpandableRow(row, { rowIndex, dataIndex })}
+
+ );
+ })
+ ) : (
+
+
+
+ {options.textLabels ? options.textLabels.body.noMatch : ""}
+
+
+
+ )}
+
+ );
+ }
+}
+
+export default withStyles(defaultBodyStyles, { name: 'MUIDataTableBodyRows' })(TableBodyRows);
diff --git a/test/MUIDataTableBody.test.js b/test/MUIDataTableBody.test.js
index 117fd68ba..35f1bc557 100644
--- a/test/MUIDataTableBody.test.js
+++ b/test/MUIDataTableBody.test.js
@@ -4,6 +4,7 @@ import { mount, shallow } from 'enzyme';
import { assert, expect, should } from 'chai';
import getTextLabels from '../src/textLabels';
import TableBody from '../src/components/TableBody';
+import TableBodyRows from '../src/components/TableBodyRows';
import TableSelectCell from '../src/components/TableSelectCell';
import Checkbox from '@material-ui/core/Checkbox';
@@ -123,7 +124,7 @@ describe('', function() {
const toggleExpandRow = () => {};
const shallowWrapper = shallow(
- ', function() {
const toggleExpandRow = () => {};
const shallowWrapper = shallow(
- ', function() {
const toggleExpandRow = () => {};
const shallowWrapper = shallow(
-