From 7a153d983d84c659140bd86365bb5dd3e2cd38c7 Mon Sep 17 00:00:00 2001 From: Patrick Gillespie Date: Sun, 26 Jul 2020 18:22:56 -0400 Subject: [PATCH 1/4] grouping first draft --- examples/Router/ExamplesGrid.js | 1 - examples/examples.js | 2 + examples/grouping/index.js | 116 ++++++++ package-lock.json | 2 +- src/MUIDataTable.js | 248 +++++++++++++---- src/components/TableBody.js | 248 ++--------------- src/components/TableBodyGroupDataRow.js | 40 +++ src/components/TableBodyGroupHeaderRow.js | 88 ++++++ src/components/TableBodyGroups.js | 154 +++++++++++ src/components/TableBodyRows.js | 316 ++++++++++++++++++++++ 10 files changed, 935 insertions(+), 280 deletions(-) create mode 100644 examples/grouping/index.js create mode 100644 src/components/TableBodyGroupDataRow.js create mode 100644 src/components/TableBodyGroupHeaderRow.js create mode 100644 src/components/TableBodyGroups.js create mode 100644 src/components/TableBodyRows.js 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..5ddd8e47d --- /dev/null +++ b/examples/grouping/index.js @@ -0,0 +1,116 @@ +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); + }, + grouping: { + columnIndexes: [1, 3] + } + }; + + 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 adf10279f..13621b180 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "mui-datatables", - "version": "3.2.0", + "version": "3.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js index 1e2d9343c..6af8961b0 100644 --- a/src/MUIDataTable.js +++ b/src/MUIDataTable.js @@ -10,6 +10,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 DefaultTableFilterList from './components/TableFilterList'; import DefaultTableFooter from './components/TableFooter'; import DefaultTableHead from './components/TableHead'; @@ -192,6 +193,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, @@ -286,6 +288,7 @@ class MUIDataTable extends React.Component { }, data: [], displayData: [], + groupingData: {}, filterData: [], filterList: [], page: 0, @@ -387,6 +390,7 @@ class MUIDataTable extends React.Component { filterType: 'dropdown', fixedHeader: true, fixedSelectColumn: true, + grouping: null, pagination: true, print: true, resizableColumns: false, @@ -875,7 +879,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, @@ -886,7 +909,9 @@ class MUIDataTable extends React.Component { count: this.options.count, data: tableData, sortOrder: sortOrder, - displayData: this.getDisplayData(columns, tableData, filterList, searchText, tableMeta, props), + displayData: nextDisplayData, + groupingData: this.getGroupingData(nextDisplayData, grouping), + grouping: grouping, columnOrder, }; @@ -1061,17 +1086,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), }; }); }; @@ -1090,6 +1118,106 @@ 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; @@ -1227,17 +1355,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, @@ -1290,12 +1421,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) { @@ -1307,13 +1443,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) { @@ -1328,18 +1469,21 @@ 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), }; }, () => { @@ -1385,19 +1529,22 @@ 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, }; }, @@ -1799,7 +1946,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; @@ -1975,6 +2122,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,103 +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..81b915c8d --- /dev/null +++ b/src/components/TableBodyGroupDataRow.js @@ -0,0 +1,40 @@ +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..b05a855fa --- /dev/null +++ b/src/components/TableBodyGroupHeaderRow.js @@ -0,0 +1,88 @@ +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..c47ca4e63 --- /dev/null +++ b/src/components/TableBodyGroups.js @@ -0,0 +1,154 @@ +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..1b79f4318 --- /dev/null +++ b/src/components/TableBodyRows.js @@ -0,0 +1,316 @@ +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.body.noMatch} + + + + )} + + ); + } +} + +export default withStyles(defaultBodyStyles, { name: 'MUIDataTableBodyRows' })(TableBodyRows); \ No newline at end of file From 2876c83601d0829aace60e1d90f34c6ad6ccaf23 Mon Sep 17 00:00:00 2001 From: Patrick Gillespie Date: Sun, 23 Aug 2020 12:07:03 -0400 Subject: [PATCH 2/4] grouping updates --- examples/grouping/index.js | 13 +++++++++++-- package-lock.json | 8 +++++--- package.json | 2 +- src/components/TableBodyRows.js | 1 + 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/examples/grouping/index.js b/examples/grouping/index.js index 5ddd8e47d..f5712d4d1 100644 --- a/examples/grouping/index.js +++ b/examples/grouping/index.js @@ -62,10 +62,19 @@ function Example() { }, onTableChange: (action, state) => { console.log(action); - console.dir(state); + //console.dir(state); + }, + onGroupExpansionChange: (group, expanded) => { + console.dir(group); + console.dir(expanded); }, grouping: { - columnIndexes: [1, 3] + columnIndexes: [1, 3], + expanded: { + "Business Consultant": { + open: true + } + } } }; 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/components/TableBodyRows.js b/src/components/TableBodyRows.js index 1b79f4318..270777343 100644 --- a/src/components/TableBodyRows.js +++ b/src/components/TableBodyRows.js @@ -267,6 +267,7 @@ class TableBodyRows extends React.Component { selectableRowsHideCheckboxes={options.selectableRowsHideCheckboxes} isRowExpanded={this.isRowExpanded(dataIndex)} isRowSelectable={isRowSelectable} + dataIndex={dataIndex} id={'MUIDataTableSelectCell-' + dataIndex} components={components} /> From be54f5767a2d3a366405e2fb86306e9f5830a16c Mon Sep 17 00:00:00 2001 From: Patrick Gillespie Date: Sun, 23 Aug 2020 12:08:24 -0400 Subject: [PATCH 3/4] formatting --- src/MUIDataTable.js | 33 +++++++---------------- src/components/TableBody.js | 2 +- src/components/TableBodyGroupDataRow.js | 8 +++--- src/components/TableBodyGroupHeaderRow.js | 29 ++++++++------------ src/components/TableBodyGroups.js | 15 +++-------- src/components/TableBodyRows.js | 2 +- 6 files changed, 28 insertions(+), 61 deletions(-) diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js index aa4fdd455..8ea74df63 100644 --- a/src/MUIDataTable.js +++ b/src/MUIDataTable.js @@ -1189,8 +1189,8 @@ class MUIDataTable extends React.Component { let map = {}; let colIndex = colIndexes[0]; data.forEach(row => { - map[ row.data[colIndex] ] = map[ row.data[colIndex] ] || []; - map[ row.data[colIndex] ].push(row); + map[row.data[colIndex]] = map[row.data[colIndex]] || []; + map[row.data[colIndex]].push(row); }); if (colIndexes.length > 1) { @@ -1209,7 +1209,7 @@ class MUIDataTable extends React.Component { onExpansionChange: () => { this.toggleGroupExpansion(nextGroup); }, - data: map[prop] + data: map[prop], }; } } @@ -1221,18 +1221,17 @@ class MUIDataTable extends React.Component { onExpansionChange: () => { this.toggleGroupExpansion(group); }, - group: group + group: group, }; } getGroupingData(displayData, grouping) { - if (!grouping) return null; - + console.log('getGroupingData'); console.log(grouping); - let cols = grouping.columnIndexes; + let cols = grouping.columnIndexes; if (!cols || cols.length === 0) return null; @@ -1495,14 +1494,7 @@ class MUIDataTable extends React.Component { let nextDisplayData = this.options.serverSide ? prevState.displayData - : this.getDisplayData( - prevState.columns, - prevState.data, - filterList, - prevState.searchText, - null, - this.props, - ); + : this.getDisplayData(prevState.columns, prevState.data, filterList, prevState.searchText, null, this.props); return { filterList: filterList, @@ -1555,14 +1547,7 @@ class MUIDataTable extends React.Component { let nextDisplayData = this.options.serverSide ? prevState.displayData - : this.getDisplayData( - prevState.columns, - prevState.data, - filterList, - prevState.searchText, - null, - this.props, - ); + : this.getDisplayData(prevState.columns, prevState.data, filterList, prevState.searchText, null, this.props); return { page: 0, @@ -1970,7 +1955,7 @@ class MUIDataTable extends React.Component { columnOrder, } = this.state; - const TableBodyComponent = this.state.groupingData ? TableBodyGroups : (TableBody || DefaultTableBody); + const TableBodyComponent = this.state.groupingData ? TableBodyGroups : TableBody || DefaultTableBody; const TableFilterListComponent = TableFilterList || DefaultTableFilterList; const TableFooterComponent = TableFooter || DefaultTableFooter; const TableHeadComponent = TableHead || DefaultTableHead; diff --git a/src/components/TableBody.js b/src/components/TableBody.js index bc0a8fb7f..2eb2db586 100644 --- a/src/components/TableBody.js +++ b/src/components/TableBody.js @@ -102,7 +102,7 @@ class TableBody extends React.Component { return ( ({ }); function TableBodyGroupDataRow(props) { - const { - row, - } = props; -console.dir(props); + const { row } = props; + console.dir(props); return ( {}; @@ -55,8 +49,8 @@ function TableBodyGroupHeaderRow(props) { [classes.expanded]: row.expanded, }); - const getLevelOffset = (level) => { - return ((level-1) * 32) + 'px'; + const getLevelOffset = level => { + return (level - 1) * 32 + 'px'; }; let bodyClasses = options.setRowProps ? options.setRowProps(row, null, null) || {} : {}; @@ -68,18 +62,17 @@ function TableBodyGroupHeaderRow(props) { [bodyClasses.className]: bodyClasses.className, [classes.tableRow]: true, })}> - + -
{row.columnLabel}:
{row.columnValue}
+
{row.columnLabel}:
+
{row.columnValue}
); diff --git a/src/components/TableBodyGroups.js b/src/components/TableBodyGroups.js index c47ca4e63..fd4b3a662 100644 --- a/src/components/TableBodyGroups.js +++ b/src/components/TableBodyGroups.js @@ -51,7 +51,6 @@ class TableBody extends React.Component { }; flattenGroups(rows, rootGroup, columns, grouping, isGroupExpanded) { - for (let prop in rootGroup.groups) { let group = rootGroup.groups[prop]; if (group.data) { @@ -72,10 +71,9 @@ class TableBody extends React.Component { rows.push({ rowType: 'data', id: group.group.join('___GROUPJOIN___') + '_data', - data: group + data: group, }); } - } else { let isExpanded = isGroupExpanded(grouping, group.group); rows.push({ @@ -93,7 +91,6 @@ class TableBody extends React.Component { if (isExpanded) { this.flattenGroups(rows, rootGroup.groups[prop], columns, grouping, isGroupExpanded); } - } } @@ -106,13 +103,7 @@ class TableBody extends React.Component { } render() { - const { - grouping, - isGroupExpanded, - classes, - columns, - groupingData, - } = this.props; + const { grouping, isGroupExpanded, classes, columns, groupingData } = this.props; let tableData = this.props.data; let rows = this.buildRows(groupingData, columns, grouping, isGroupExpanded); @@ -123,7 +114,7 @@ class TableBody extends React.Component { return ( {rows.map((data, rowIndex) => { - let RowComponent = (data.rowType === 'group') ? TableBodyGroupHeaderRow : TableBodyGroupDataRow; + let RowComponent = data.rowType === 'group' ? TableBodyGroupHeaderRow : TableBodyGroupDataRow; return ( Date: Sun, 23 Aug 2020 12:14:51 -0400 Subject: [PATCH 4/4] test update --- src/MUIDataTable.js | 8 ++++---- src/components/TableBodyRows.js | 2 +- test/MUIDataTableBody.test.js | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js index 8ea74df63..6b562f78a 100644 --- a/src/MUIDataTable.js +++ b/src/MUIDataTable.js @@ -1228,16 +1228,16 @@ class MUIDataTable extends React.Component { getGroupingData(displayData, grouping) { if (!grouping) return null; - console.log('getGroupingData'); - console.log(grouping); + //console.log('getGroupingData'); + //console.log(grouping); let cols = grouping.columnIndexes; if (!cols || cols.length === 0) return null; - console.dir(displayData); + //console.dir(displayData); let groups = this.getGroups(grouping, cols, displayData, 1, []); - console.dir(groups); + //console.dir(groups); return groups; } diff --git a/src/components/TableBodyRows.js b/src/components/TableBodyRows.js index c131281ae..281ecc9c0 100644 --- a/src/components/TableBodyRows.js +++ b/src/components/TableBodyRows.js @@ -304,7 +304,7 @@ class TableBodyRows extends React.Component { colIndex={0} rowIndex={0}> - {options.textLabels.body.noMatch} + {options.textLabels ? options.textLabels.body.noMatch : ""} 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( -