diff --git a/README.md b/README.md
index 4fb4f6013..605fe2a0d 100644
--- a/README.md
+++ b/README.md
@@ -145,7 +145,7 @@ The component accepts the following props:
|:--:|:-----|:-----|
|**`title`**|array|Title used to caption table
|**`columns`**|array|Columns used to describe table. Must be either an array of simple strings or objects describing a column
-|**`data`**|array|Data used to describe table. Must be either an array containing objects of key/value pairs with values that are strings or numbers, or arrays of strings or numbers (Ex: data: [{"Name": "Joe", "Job Title": "Plumber", "Age": 30}, {"Name": "Jane", "Job Title": "Electrician", "Age": 45}] or data: [["Joe", "Plumber", 30], ["Jane", "Electrician", 45]])
+|**`data`**|array|Data used to describe table. Must be either an array containing objects of key/value pairs with values that are strings or numbers, or arrays of strings or numbers (Ex: data: [{"Name": "Joe", "Job Title": "Plumber", "Age": 30}, {"Name": "Jane", "Job Title": "Electrician", "Age": 45}] or data: [["Joe", "Plumber", 30], ["Jane", "Electrician", 45]]). The **customBodyRender** and **customBodyRenderLite** options can be used to control the data diaplay.
|**`options`**|object|Options used to describe table
|**`components`**|object|Custom components used to render the table
@@ -154,6 +154,7 @@ The component accepts the following props:
|:--:|:-----|:--|:-----|
|**`caseSensitive `**|boolean|false|Enable/disable case sensitivity for search.
|**`confirmFilters`**|boolean|false|Works in conjunction with the **customFilterDialogFooter** option and makes it so filters have to be confirmed before being applied to the table. When this option is true, the customFilterDialogFooter callback will receive an applyFilters function which, when called, will apply the filters to the table. [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/serverside-filters/index.js)
+|**`columnOrder`**|array||An array of numbers (column indices) indicating the order the columns should be displayed in. Defaults to the order provided by the Columns prop. This option is useful if you'd like certain columns to swap positions (see draggableColumns option).
|**`count`**|number||User provided override for total number of rows.
|**`customFilterDialogFooter `**|function||Add a custom footer to the filter dialog. `customFilterDialogFooter(curentFilterList: array, applyFilters: function) => React Component`
|**`customFooter`**|function||Render a custom table footer. `function(count, page, rowsPerPage, changeRowsPerPage, changePage, `[`textLabels: object`](https://github.com/gregnb/mui-datatables/blob/master/src/textLabels.js)`) => string`|` React Component` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/customize-footer/index.js)
@@ -165,8 +166,9 @@ The component accepts the following props:
|**`customToolbar`**|function||Render a custom toolbar
|**`customToolbarSelect`**|function||Render a custom selected rows toolbar. `function(selectedRows, displayData, setSelectedRows) => void`
|**`download`**|boolean|true|Show/hide download icon from toolbar
-|**`downloadOptions`**|object|`{filename: 'tableDownload.csv', separator: ','}`|Options to change the output of the CSV file: `filename`: string, `separator`: string, `filterOptions`: object(`useDisplayedColumnsOnly`: boolean, `useDisplayedRowsOnly`: boolean)
-|**`elevation`**|number|4|Shadow depth applied to Paper component.
+|**`downloadOptions`**|object|`{filename: 'tableDownload.csv', separator: ','}`|An object of options to change the output of the CSV file:
`filename`: string `separator`: string `filterOptions`: object`useDisplayedColumnsOnly`: boolean `useDisplayedRowsOnly`: boolean
+|**`draggableColumns`**|object|{}|An object of options describing how dragging columns should work. The options are:
`enabled:boolean`: Indicates if draggable columns are enabled. Defaults to false. `transitionTime:number`: The time in milliseconds it takes for columns to swap positions. Defaults to 300. To disable the dragging of a particular column, see the "draggable" option in the columns options. Dragging a column to a new position updates the columnOrder array and triggers the onColumnOrderChange callback.
+|**`elevation`**|number|4|Shadow depth applied to Paper component.
|**`enableNestedDataAccess`**|string|""|If provided a non-empty string (ex: "."), it will use that value in the column's names to access nested data. For example, given a enableNestedDataAccess value of "." and a column name of "phone.cell", the column would use the value found in `phone:{cell:"555-5555"}`. Any amount of nesting will work. [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/data-as-objects/index.js) demonstrates the functionality.
|**`expandableRows`**|boolean|false|Enable/disable expandable rows.
|**`expandableRowsHeader`**|boolean|true|Show/hide the expand all/collapse all row header for expandable rows.
@@ -180,9 +182,11 @@ The component accepts the following props:
|**`onCellClick`**|function||Callback function that triggers when a cell is clicked. `function(colData: any, cellMeta: { colIndex: number, rowIndex: number, dataIndex: number }) => void`
|**`onChangePage`**|function||Callback function that triggers when a page has changed. `function(currentPage: number) => void`
|**`onChangeRowsPerPage`**|function||Callback function that triggers when the number of rows per page has changed. `function(numberOfRows: number) => void`
+|**`onColumnOrderChange`**|function||Callback function that triggers when a column has been dragged to a new location. `function(newColumnOrder:array, columnIndex:number, newPosition:number) => void`
|**`onColumnSortChange`**|function||Callback function that triggers when a column has been sorted. `function(changedColumn: string, direction: string) => void`
|**`onDownload`**|function||A callback function that triggers when the user downloads the CSV file. In the callback, you can control what is written to the CSV file. This method can be used to add the Excel specific BOM character (see this [example](https://github.com/gregnb/mui-datatables/pull/722#issuecomment-526346440)). `function(buildHead: (columns) => string, buildBody: (data) => string, columns, data) => string`. Return `false` to cancel download of file.
|**`onFilterChange`**|function||Callback function that triggers when filters have changed. `function(changedColumn: string, filterList: array, type: enum('checkbox', 'dropdown', 'multiselect', 'textField', 'custom', 'chip', 'reset'), changedColumnIndex, displayData) => void`
+|**`onFilterChipClose`**|function||Callback function that is triggered when a user clicks the "X" on a filter chip. `function(index : number, removedFilter : string, filterList : array) => void` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/serverside-filters/index.js)
|**`onFilterConfirm`**|function||Callback function that is triggered when a user presses the "confirm" button on the filter popover. This occurs only if you've set **confirmFilters** option to true. `function(filterList: array) => void` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/serverside-filters/index.js)
|**`onFilterDialogClose`**|function||Callback function that triggers when the filter dialog closes. `function() => void`
|**`onFilterDialogOpen`**|function||Callback function that triggers when the filter dialog opens. `function() => void`
@@ -256,15 +260,17 @@ const columns = [
#### Column Options:
|Name|Type|Default|Description
|:--:|:-----|:--|:-----|
-|**`customBodyRender`**|function||Function that returns a string or React component. Used as display data within all table cells of a given column. `function(value, tableMeta, updateValue) => string`|` React Component` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/component/index.js)
+|**`customBodyRender`**|function||Function that returns a string or React component. Used to display data within all table cells of a given column. The value returned from this function will be used for filtering in the filter dialog. If this isn't need, you may want to consider customBodyRenderLite instead. `function(value, tableMeta, updateValue) => string`|` React Component` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/component/index.js)
+|**`customBodyRenderLite`**|function||Function that returns a string or React component. Used to display data within all table cells of a given column. This method performs better than customBodyRender but has the following caveats:
The value returned from this function is **not** used for filtering, so the filter dialog will use the raw data from the data array. This method only gives you the dataIndex and rowIndex, leaving you to lookup the column value. `function(dataIndex, rowIndex) => string`|` React Component` [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/large-data-set/index.js)
|**`customFilterListOptions`**|{render: function, update: function}|| (These options only affect the filter chips that display after filters are selected. To modify the filters themselves, see `filterOptions`) `render` returns a string or array of strings used as the chip label(s). `function(value) => string OR arrayOfStrings`, `update` returns a `filterList (see above)` allowing for custom filter updates when removing the filter chip [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/column-filters/index.js)
|**`customHeadRender`**|function||Function that returns a string or React component. Used as display for column header. `function(columnMeta, handleToggleColumn, sortOrder) => string`|` React Component`
|**`display`**|string|'true'|Display column in table. `enum('true', 'false', 'excluded')`
|**`download`**|boolean|true|Display column in CSV download file.
+|**`draggable`**|boolean|true|Determines if a column can be dragged. The draggableColumns.enabled option must also be true.
|**`empty`**|boolean|false|This denotes whether the column has data or not (for use with intentionally empty columns).
|**`filter`**|boolean|true|Display column in filter list.
|**`filterList`**|array||Filter value list [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/column-filters/index.js)
-|**`filterOptions`**|{names, logic, display(filterList, onChange(filterList, index, column), index, column, filterData), renderValue}||(These options affect the filter display and functionality from the filter dialog. To modify the filter chips that display after selecting filters, see `customFilterListOptions`) With filter options, it's possible to use custom names for the filter fields [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/column-filters/index.js), custom filter logic [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/customize-filter/index.js), and custom rendering [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/customize-filter/index.js). `filterList` must be of the same type in the main column options, that is an array of arrays, where each array corresponds to the filter list for a given column.
+|**`filterOptions`**|object||These options affect the filter display and functionality from the filter dialog. To modify the filter chips that display after selecting filters, see `customFilterListOptions`
This option is an object of several options for customizing the filter display and how filtering works.
names: custom names for the filter fields [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/column-filters/index.js) logic: custom filter logic [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/customize-filter/index.js) display(filterList, onChange(filterList, index, column), index, column, filterData): Custom rendering inside the filter dialog [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/customize-filter/index.js). `filterList` must be of the same type in the main column options, that is an array of arrays, where each array corresponds to the filter list for a given column. renderValue: A function to customize filter choices [Example](https://github.com/gregnb/mui-datatables/blob/master/examples/customize-filter/index.js). Example use case: changing empty strings to "(empty)" in a dropdown. fullWidth (boolean): Will force a filter option to take up the grid's full width.
|**`filterType `**|string|'dropdown'|Choice of filtering view. Takes priority over global filterType option.`enum('checkbox', 'dropdown', 'multiselect', 'textField', 'custom')` Use 'custom' if you are supplying your own rendering via `filterOptions`.
|**`hint`**|string||Display hint icon with string as tooltip on hover.
|**`print`**|boolean|true|Display column when printing.
diff --git a/examples/array-value-columns/index.js b/examples/array-value-columns/index.js
index 4cd661a52..15e97fa92 100644
--- a/examples/array-value-columns/index.js
+++ b/examples/array-value-columns/index.js
@@ -48,13 +48,14 @@ class Example extends React.Component {
options: {
filter: true,
filterType: 'multiselect',
- customBodyRender: (value) => {
+ customBodyRenderLite: (dataIndex) => {
+ let value = data[dataIndex][5];
return value.map( (val, key) => {
return ;
});
- }
+ },
}
- }
+ },
];
diff --git a/examples/column-filters/index.js b/examples/column-filters/index.js
index 427862fdc..2cd3e8221 100644
--- a/examples/column-filters/index.js
+++ b/examples/column-filters/index.js
@@ -36,9 +36,8 @@ class Example extends React.Component {
{
name: "Age",
options: {
- customBodyRender: (val, tableMeta) => {
- console.log(val);
- console.dir(tableMeta);
+ customBodyRenderLite: (dataIndex) => {
+ let val = data[dataIndex][3];
return val;
},
filter: true,
diff --git a/examples/column-options-update/index.js b/examples/column-options-update/index.js
index 9f62b2f7b..14b0e78c5 100644
--- a/examples/column-options-update/index.js
+++ b/examples/column-options-update/index.js
@@ -12,7 +12,7 @@ class Example extends React.Component {
[],
[]
],
- filterOptions: ['this', 'test', 'is', 'working'],
+ filterOptions: ['Franky Miles', 'this', 'test', 'is', 'working'],
display: ['true', 'true', 'true', 'true', 'true'],
data: [
["Gabby George", "Business Analyst", "Minneapolis", 30, 100000],
@@ -93,7 +93,10 @@ class Example extends React.Component {
name: "Location",
options: {
display: this.state.display[2],
- filter: false,
+ filter: true,
+ filterOptions: {
+ fullWidth: true,
+ },
filterList: filterList[2].length ? filterList[2] : null,
}
},
diff --git a/examples/csv-export/index.js b/examples/csv-export/index.js
index 2dd1faf5a..b0db25014 100644
--- a/examples/csv-export/index.js
+++ b/examples/csv-export/index.js
@@ -21,7 +21,10 @@ class Example extends React.Component {
{
name: 'Age',
options: {
- customBodyRender: value => {value}
+ customBodyRenderLite: (dataIndex) => {
+ let value = data[dataIndex][3];
+ return {value}
;
+ }
}
},
{
diff --git a/examples/custom-action-columns/index.js b/examples/custom-action-columns/index.js
index f731905d0..0d0e234c2 100644
--- a/examples/custom-action-columns/index.js
+++ b/examples/custom-action-columns/index.js
@@ -50,7 +50,7 @@ class Example extends React.Component {
filter: false,
sort: false,
empty: true,
- customBodyRender: (value, tableMeta, updateValue) => {
+ customBodyRenderLite: (dataIndex) => {
return (
{
const { data } = this.state;
@@ -69,9 +69,9 @@ class Example extends React.Component {
filter: false,
sort: false,
empty: true,
- customBodyRender: (value, tableMeta, updateValue) => {
+ customBodyRenderLite: (dataIndex, rowIndex) => {
return (
- window.alert(`Clicked "Edit" for row ${tableMeta.rowIndex}`)}>
+ window.alert(`Clicked "Edit" for row ${rowIndex} with dataIndex of ${dataIndex}`)}>
Edit
);
@@ -116,7 +116,7 @@ class Example extends React.Component {
filter: false,
sort: false,
empty: true,
- customBodyRender: (value, tableMeta, updateValue) => {
+ customBodyRenderLite: (dataIndex) => {
return (
{
const { data } = this.state;
@@ -168,7 +168,6 @@ class Example extends React.Component {
filter: true,
filterType: 'dropdown',
responsive: 'vertical',
- page: 2,
onColumnSortChange: (changedColumn, direction) => console.log('changedColumn: ', changedColumn, 'direction: ', direction),
onChangeRowsPerPage: numberOfRows => console.log('numberOfRows: ', numberOfRows),
onChangePage: currentPage => console.log('currentPage: ', currentPage)
diff --git a/examples/custom-components/index.js b/examples/custom-components/index.js
index dc90b5645..22bd73c23 100644
--- a/examples/custom-components/index.js
+++ b/examples/custom-components/index.js
@@ -87,11 +87,18 @@ class Example extends React.Component {
['James Houston', 'Test Corp', 'Dallas', 'TX'],
];
+ let options = {
+ onFilterChipClose: (index, removedFilter, filterList) => {
+ console.log(index, removedFilter, filterList);
+ }
+ };
+
return (
+
+ Responsive Option
+ setResponsive(e.target.value)}
+ >
+ vertical
+ standard
+ simple
+
+ scroll (deprecated)
+ scrollMaxHeight (deprecated)
+ stacked (deprecated)
+
+
+
+ Table Body Height
+ setTableBodyHeight(e.target.value)}
+ >
+ [blank]
+ 400px
+ 800px
+ 100%
+
+
+
+ Max Table Body Height
+ setTableBodyMaxHeight(e.target.value)}
+ >
+ [blank]
+ 400px
+ 800px
+ 100%
+
+
+
+ setTransitionTime(e.target.value)} />
+
+
+ >
+ );
+}
+
+export default Example;
\ No newline at end of file
diff --git a/examples/examples.js b/examples/examples.js
index f1be7189e..d85cb2c50 100644
--- a/examples/examples.js
+++ b/examples/examples.js
@@ -15,6 +15,7 @@ import CustomizeStyling from './customize-styling';
import CustomizeToolbar from './customize-toolbar';
import CustomizeToolbarSelect from './customize-toolbarselect';
import DataAsObjects from './data-as-objects';
+import DraggableColumns from './draggable-columns';
import ExpandableRows from './expandable-rows';
import FixedHeader from './fixed-header';
import HideColumnsPrint from './hide-columns-print';
@@ -53,18 +54,19 @@ export default {
'Customize Toolbar': CustomizeToolbar,
'Customize Toolbar Select': CustomizeToolbarSelect,
'Data As Objects': DataAsObjects,
+ 'Draggable Columns': DraggableColumns,
'Expandable Rows': ExpandableRows,
'Fixed Header': FixedHeader,
'Hide Columns Print': HideColumnsPrint,
'Large Data Set': LargeDataSet,
- OnDownload: OnDownload,
- OnTableInit: OnTableInit,
+ 'OnDownload': OnDownload,
+ 'OnTableInit': OnTableInit,
'Resizable Columns': ResizableColumns,
'Selectable Rows': SelectableRows,
'ServerSide Filters': ServerSideFilters,
'ServerSide Pagination': ServerSidePagination,
'ServerSide Sorting': ServerSideSorting,
- Simple: Simple,
+ 'Simple': Simple,
'Simple No Toolbar': SimpleNoToolbar,
'Text Localization': TextLocalization,
'Custom Components': CustomComponents,
diff --git a/examples/expandable-rows/index.js b/examples/expandable-rows/index.js
index 560e57270..e83927060 100644
--- a/examples/expandable-rows/index.js
+++ b/examples/expandable-rows/index.js
@@ -82,7 +82,7 @@ class Example extends React.Component {
filterType: 'dropdown',
responsive: 'standard',
expandableRows: true,
- expandableRowsHeader: true,
+ expandableRowsHeader: false,
expandableRowsOnClick: true,
isRowExpandable: (dataIndex, expandedRows) => {
// Prevent expand/collapse of any row if there are 4 rows expanded already (but allow those already expanded to be collapsed)
diff --git a/examples/large-data-set/index.js b/examples/large-data-set/index.js
index 114ce23b9..78f940ab4 100644
--- a/examples/large-data-set/index.js
+++ b/examples/large-data-set/index.js
@@ -51,7 +51,8 @@ class Example extends React.Component {
label: "Name",
options: {
filter: true,
- customBodyRender: (val) => {
+ customBodyRenderLite: (dataIndex) => {
+ let val = this.state.data[dataIndex].name;
return val;
}
}
@@ -61,7 +62,8 @@ class Example extends React.Component {
label: "Modified Title Label",
options: {
filter: true,
- customBodyRender: (val, tableMeta) => {
+ customBodyRenderLite: (dataIndex) => {
+ let val = this.state.data[dataIndex].title;
return val;
}
}
@@ -86,7 +88,8 @@ class Example extends React.Component {
options: {
filter: true,
sort: false,
- customBodyRender: (val) => {
+ customBodyRenderLite: (dataIndex) => {
+ let val = this.state.data[dataIndex].salary;
return val;
}
}
@@ -97,7 +100,8 @@ class Example extends React.Component {
options: {
filter: true,
sort: false,
- customBodyRender: (val) => {
+ customBodyRenderLite: (dataIndex) => {
+ let val = this.state.data[dataIndex].phone;
return val;
}
}
@@ -108,7 +112,8 @@ class Example extends React.Component {
options: {
filter: true,
sort: false,
- customBodyRender: (val) => {
+ customBodyRenderLite: (dataIndex) => {
+ let val = this.state.data[dataIndex].email;
return val;
}
}
@@ -144,4 +149,4 @@ class Example extends React.Component {
}
}
-export default Example;
+export default Example;
\ No newline at end of file
diff --git a/examples/resizable-columns/index.js b/examples/resizable-columns/index.js
index 6ef774901..776e33aa1 100644
--- a/examples/resizable-columns/index.js
+++ b/examples/resizable-columns/index.js
@@ -18,6 +18,7 @@ function Example(props) {
{
name: "Counter",
options: {
+ sort: false,
empty: true,
customBodyRender: value => +
}
@@ -25,7 +26,6 @@ function Example(props) {
{
name: "Name",
options: {
- sort: false,
hint: "?",
setCellProps: () => ({style: {whiteSpace:'nowrap'}})
}
@@ -78,6 +78,9 @@ function Example(props) {
filter: true,
filterType: 'dropdown',
resizableColumns: true,
+ draggableColumns: {
+ enabled: true,
+ }
};
return (
diff --git a/package-lock.json b/package-lock.json
index 49a2fd68f..4c43d19d2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "mui-datatables",
- "version": "3.0.0-beta.2",
+ "version": "3.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2765,6 +2765,21 @@
"integrity": "sha512-9nKENeWRI6kQk44TbeqleIVtNLfcS3klVUepzl/ZCqzR5Bi06uqBCD277hdVvG/wL1pxA+R/pgJQLqnF5E2wPQ==",
"dev": true
},
+ "@react-dnd/asap": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.0.tgz",
+ "integrity": "sha512-0XhqJSc6pPoNnf8DhdsPHtUhRzZALVzYMTzRwV4VI6DJNJ/5xxfL9OQUwb8IH5/2x7lSf7nAZrnzUD+16VyOVQ=="
+ },
+ "@react-dnd/invariant": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz",
+ "integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw=="
+ },
+ "@react-dnd/shallowequal": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz",
+ "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg=="
+ },
"@rollup/plugin-babel": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.0.2.tgz",
@@ -2947,6 +2962,30 @@
"@types/node": "*"
}
},
+ "@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "requires": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ },
+ "dependencies": {
+ "hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "requires": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ }
+ }
+ },
"@types/json-schema": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
@@ -2968,8 +3007,7 @@
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
- "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
- "dev": true
+ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
},
"@types/q": {
"version": "1.5.4",
@@ -2981,7 +3019,6 @@
"version": "16.9.35",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.35.tgz",
"integrity": "sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ==",
- "dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
@@ -5528,8 +5565,7 @@
"csstype": {
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
- "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==",
- "dev": true
+ "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w=="
},
"cyclist": {
"version": "1.0.1",
@@ -5868,6 +5904,16 @@
"integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=",
"dev": true
},
+ "dnd-core": {
+ "version": "11.1.3",
+ "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz",
+ "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==",
+ "requires": {
+ "@react-dnd/asap": "^4.0.0",
+ "@react-dnd/invariant": "^2.0.0",
+ "redux": "^4.0.4"
+ }
+ },
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@@ -13914,6 +13960,40 @@
"prop-types": "^15.6.2"
}
},
+ "react-dnd": {
+ "version": "11.1.3",
+ "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-11.1.3.tgz",
+ "integrity": "sha512-8rtzzT8iwHgdSC89VktwhqdKKtfXaAyC4wiqp0SywpHG12TTLvfOoL6xNEIUWXwIEWu+CFfDn4GZJyynCEuHIQ==",
+ "requires": {
+ "@react-dnd/shallowequal": "^2.0.0",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "dnd-core": "^11.1.3",
+ "hoist-non-react-statics": "^3.3.0"
+ },
+ "dependencies": {
+ "hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "requires": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ }
+ }
+ },
+ "react-dnd-html5-backend": {
+ "version": "11.1.3",
+ "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-11.1.3.tgz",
+ "integrity": "sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw==",
+ "requires": {
+ "dnd-core": "^11.1.3"
+ }
+ },
"react-dom": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
@@ -14340,6 +14420,15 @@
}
}
},
+ "redux": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
+ "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "symbol-observable": "^1.2.0"
+ }
+ },
"reflect.ownkeys": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz",
@@ -16133,6 +16222,11 @@
}
}
},
+ "symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
+ },
"symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
diff --git a/package.json b/package.json
index 75bc9ecea..82547607b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mui-datatables",
- "version": "3.0.1",
+ "version": "3.1.0",
"description": "Datatables for React using Material-UI",
"main": "dist/index.js",
"files": [
@@ -103,6 +103,8 @@
"lodash.memoize": "^4.1.2",
"lodash.merge": "^4.6.2",
"prop-types": "^15.7.2",
+ "react-dnd": "^11.1.3",
+ "react-dnd-html5-backend": "^11.1.3",
"react-to-print": "^2.8.0",
"webpack-cli": "^3.3.11"
},
diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js
index b32bbde7c..fcbdb953e 100644
--- a/src/MUIDataTable.js
+++ b/src/MUIDataTable.js
@@ -19,6 +19,8 @@ import DefaultTableToolbarSelect from './components/TableToolbarSelect';
import MuiTooltip from '@material-ui/core/Tooltip';
import getTextLabels from './textLabels';
import { buildMap, getCollatorComparator, sortCompare, getPageValue, warnDeprecated, warnInfo } from './utils';
+import { DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
const defaultTableStyles = theme => ({
root: {},
@@ -138,6 +140,7 @@ class MUIDataTable extends React.Component {
filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect', 'textField', 'custom']),
customHeadRender: PropTypes.func,
customBodyRender: PropTypes.func,
+ customBodyRenderLite: PropTypes.func,
customFilterListOptions: PropTypes.oneOfType([
PropTypes.shape({
render: PropTypes.func,
@@ -154,6 +157,7 @@ class MUIDataTable extends React.Component {
/** Options used to describe table */
options: PropTypes.shape({
caseSensitive: PropTypes.bool,
+ columnOrder: PropTypes.array,
count: PropTypes.number,
confirmFilters: PropTypes.bool,
consoleWarnings: PropTypes.bool,
@@ -165,6 +169,7 @@ class MUIDataTable extends React.Component {
customSort: PropTypes.func,
customToolbar: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
customToolbarSelect: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
+ draggableColumns: PropTypes.object,
enableNestedDataAccess: PropTypes.string,
expandableRows: PropTypes.bool,
expandableRowsHeader: PropTypes.bool,
@@ -188,6 +193,8 @@ class MUIDataTable extends React.Component {
isRowSelectable: PropTypes.func,
onDownload: PropTypes.func,
onFilterChange: PropTypes.func,
+ onFilterChipClose: PropTypes.func,
+ onFilterConfirm: PropTypes.func,
onFilterDialogOpen: PropTypes.func,
onFilterDialogClose: PropTypes.func,
onRowClick: PropTypes.func,
@@ -282,7 +289,9 @@ class MUIDataTable extends React.Component {
super();
this.tableRef = React.createRef();
this.tableContent = React.createRef();
- this.headCellRefs = {};
+ this.draggableHeadCellRefs = {};
+ this.resizeHeadCellRefs = {};
+ this.timers = {};
this.setHeadResizeable = () => {};
this.updateDividers = () => {};
}
@@ -292,7 +301,7 @@ class MUIDataTable extends React.Component {
}
componentDidMount() {
- this.setHeadResizeable(this.headCellRefs, this.tableRef);
+ this.setHeadResizeable(this.resizeHeadCellRefs, this.tableRef);
// When we have a search, we must reset page to view it unless on serverSide since paging is handled by the user.
if (this.props.options.searchText && !this.props.options.serverSide) this.setState({ page: 0 });
@@ -325,7 +334,7 @@ class MUIDataTable extends React.Component {
this.options.resizableColumns === true ||
(this.options.resizableColumns && this.options.resizableColumns.enabled)
) {
- this.setHeadResizeable(this.headCellRefs, this.tableRef);
+ this.setHeadResizeable(this.resizeHeadCellRefs, this.tableRef);
this.updateDividers();
}
}
@@ -363,6 +372,10 @@ class MUIDataTable extends React.Component {
filename: 'tableDownload.csv',
separator: ',',
},
+ draggableColumns: {
+ enabled: false,
+ transitionTime: 300,
+ },
elevation: 4,
enableNestedDataAccess: '',
expandableRows: false,
@@ -536,8 +549,9 @@ class MUIDataTable extends React.Component {
this.setState(optState);
}
- setHeadCellRef = (index, el) => {
- this.headCellRefs[index] = el;
+ setHeadCellRef = (index, pos, el) => {
+ this.draggableHeadCellRefs[index] = el;
+ this.resizeHeadCellRefs[pos] = el;
};
// must be arrow function on local field to refer to the correct instance when passed around
@@ -546,12 +560,18 @@ class MUIDataTable extends React.Component {
/*
* Build the source table data
+ *
+ * newColumns - columns from the options object.
+ * prevColumns - columns object saved onto ths state.
+ * newColumnOrder - columnOrder from the options object.
+ * prevColumnOrder - columnOrder object saved onto the state.
*/
- buildColumns = (newColumns, prevColumns) => {
+ buildColumns = (newColumns, prevColumns, newColumnOrder, prevColumnOrder) => {
let columnData = [];
let filterData = [];
let filterList = [];
+ let columnOrder = [];
newColumns.forEach((column, colIndex) => {
let columnOptions = {
@@ -565,6 +585,7 @@ class MUIDataTable extends React.Component {
viewColumns: true,
};
+ columnOrder.push(colIndex);
const options = { ...column.options };
if (typeof column === 'object') {
@@ -611,7 +632,18 @@ class MUIDataTable extends React.Component {
filterList[colIndex] = [];
});
- return { columns: columnData, filterData, filterList };
+ if (Array.isArray(newColumnOrder)) {
+ columnOrder = newColumnOrder;
+ } else if (
+ Array.isArray(prevColumnOrder) &&
+ Array.isArray(newColumns) &&
+ Array.isArray(prevColumns) &&
+ newColumns.length === prevColumns.length
+ ) {
+ columnOrder = prevColumnOrder;
+ }
+
+ return { columns: columnData, filterData, filterList, columnOrder };
};
transformData = (columns, data) => {
@@ -642,22 +674,17 @@ class MUIDataTable extends React.Component {
})
: data.map(row => columns.map(col => leaf(row, col.name)));
- // We need to determine if object data exists in the transformed structure, as this is currently not allowed and will cause errors if not handled by a custom renderer
- const hasInvalidData =
- transformedData.filter(
- data => data.filter(d => typeof d === 'object' && d !== null && !Array.isArray(d)).length > 0,
- ).length > 0;
- if (hasInvalidData)
- this.warnDep(
- 'Passing objects in as data is not supported. Consider using ids in your data and linking it to external objects if you want to access object data from custom render functions.',
- );
-
return transformedData;
};
setTableData(props, status, dataUpdated, callback = () => {}) {
let tableData = [];
- let { columns, filterData, filterList } = this.buildColumns(props.columns, this.state.columns);
+ let { columns, filterData, filterList, columnOrder } = this.buildColumns(
+ props.columns,
+ this.state.columns,
+ this.options.columnOrder,
+ this.state.columnOrder,
+ );
let sortIndex = null;
let sortDirection = 'none';
let tableMeta;
@@ -697,26 +724,41 @@ class MUIDataTable extends React.Component {
});
}
- if (typeof column.customBodyRender === 'function') {
- const rowData = tableData[rowIndex].data;
- tableMeta = this.getTableMeta(rowIndex, colIndex, rowData, column, data, this.state);
- const funcResult = column.customBodyRender(value, tableMeta);
+ if (column.filter !== false) {
+ if (typeof column.customBodyRender === 'function') {
+ const rowData = tableData[rowIndex].data;
+ tableMeta = this.getTableMeta(rowIndex, colIndex, rowData, column, data, this.state);
+ const funcResult = column.customBodyRender(value, tableMeta);
- if (React.isValidElement(funcResult) && funcResult.props.value) {
- value = funcResult.props.value;
- } else if (typeof funcResult === 'string') {
- value = funcResult;
+ if (React.isValidElement(funcResult) && funcResult.props.value) {
+ value = funcResult.props.value;
+ } else if (typeof funcResult === 'string') {
+ value = funcResult;
+ }
}
- }
- if (filterData[colIndex].indexOf(value) < 0 && !Array.isArray(value)) {
- filterData[colIndex].push(value);
- } else if (Array.isArray(value)) {
- value.forEach(element => {
- if (filterData[colIndex].indexOf(element) < 0) {
- filterData[colIndex].push(element);
- }
- });
+ if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
+ // it's extremely rare but possible to create an object without a toString method, ex: var x = Object.create(null);
+ // so this check has to be made
+ value = value.toString ? value.toString() : '';
+ }
+
+ if (filterData[colIndex].indexOf(value) < 0 && !Array.isArray(value)) {
+ filterData[colIndex].push(value);
+ } else if (Array.isArray(value)) {
+ value.forEach(element => {
+ let elmVal;
+ if ((typeof element === 'object' && element !== null) || typeof element === 'function') {
+ elmVal = element.toString ? element.toString() : '';
+ } else {
+ elmVal = element;
+ }
+
+ if (filterData[colIndex].indexOf(elmVal) < 0) {
+ filterData[colIndex].push(elmVal);
+ }
+ });
+ }
}
}
@@ -847,6 +889,7 @@ class MUIDataTable extends React.Component {
data: tableData,
sortOrder: sortOrder,
displayData: this.getDisplayData(columns, tableData, filterList, searchText, tableMeta),
+ columnOrder,
},
callback,
);
@@ -865,7 +908,9 @@ class MUIDataTable extends React.Component {
let columnValue = row[index];
let column = columns[index];
- if (column.customBodyRender) {
+ if (column.customBodyRenderLite) {
+ displayRow.push(column.customBodyRenderLite);
+ } else if (column.customBodyRender) {
const tableMeta = this.getTableMeta(rowIndex, index, row, column, dataForTableMeta, {
...this.state,
filterList: filterList,
@@ -886,9 +931,11 @@ class MUIDataTable extends React.Component {
: funcResult.props && funcResult.props.value
? funcResult.props.value
: columnValue;
- }
- displayRow.push(columnDisplay);
+ displayRow.push(columnDisplay);
+ } else {
+ displayRow.push(columnDisplay);
+ }
const columnVal = columnValue === null || columnValue === undefined ? '' : columnValue.toString();
@@ -1323,6 +1370,22 @@ class MUIDataTable extends React.Component {
return this.state.expandedRows.data.length === this.state.data.length;
};
+ updateColumnOrder = (columnOrder, columnIndex, newPosition) => {
+ this.setState(
+ prevState => {
+ return {
+ columnOrder,
+ };
+ },
+ () => {
+ this.setTableAction('columnOrderChange');
+ if (this.options.onColumnOrderChange) {
+ this.options.onColumnOrderChange(this.state.columnOrder, columnIndex, newPosition);
+ }
+ },
+ );
+ };
+
selectRowDelete = () => {
const { selectedRows, data, filterList } = this.state;
@@ -1624,6 +1687,7 @@ class MUIDataTable extends React.Component {
searchText,
sortOrder,
serverSideFilterList,
+ columnOrder,
} = this.state;
const TableBodyComponent = TableBody || DefaultTableBody;
@@ -1712,6 +1776,7 @@ class MUIDataTable extends React.Component {
showToolbar && (
(this.updateDividers = fn)}
setResizeable={fn => (this.setHeadResizeable = fn)}
options={this.props.options}
/>
)}
- (this.tableRef = el)}
- tabIndex={'0'}
- role={'grid'}
- className={tableClassNames}
- {...tableProps}>
- {title}
- (this.updateToolbarSelect = fn)}
- selectedRows={selectedRows}
- selectRowUpdate={this.selectRowUpdate}
- toggleSort={this.toggleSortColumn}
- setCellRef={this.setHeadCellRef}
- expandedRows={expandedRows}
- areAllRowsExpanded={this.areAllRowsExpanded}
- toggleAllExpandableRows={this.toggleAllExpandableRows}
- options={this.options}
- sortOrder={sortOrder}
- components={this.props.components}
- />
-
- {this.options.customTableBodyFooterRender
- ? this.options.customTableBodyFooterRender({
- data: displayData,
- count: rowCount,
- columns,
- selectedRows,
- selectableRows: this.options.selectableRows,
- })
- : null}
-
+
+ (this.tableRef = el)}
+ tabIndex={'0'}
+ role={'grid'}
+ className={tableClassNames}
+ {...tableProps}>
+ {title}
+
+
+ {this.options.customTableBodyFooterRender
+ ? this.options.customTableBodyFooterRender({
+ data: displayData,
+ count: rowCount,
+ columns,
+ selectedRows,
+ selectableRows: this.options.selectableRows,
+ })
+ : null}
+
+
{
+ 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 } = this.props;
+ const {
+ classes,
+ columns,
+ toggleExpandRow,
+ options,
+ columnOrder = this.props.columns.map((item, idx) => idx),
+ } = this.props;
const tableRows = this.buildRows();
const visibleColCnt = columns.filter(c => c.display === 'true').length;
@@ -229,6 +246,8 @@ class TableBody extends React.Component {
let isRowSelectable = this.isRowSelectable(dataIndex);
let bodyClasses = options.setRowProps ? options.setRowProps(row, dataIndex, rowIndex) : {};
+ const processedRow = this.processRow(row, columnOrder);
+
return (
- {row.map(
- (column, columnIndex) =>
- columns[columnIndex].display === 'true' && (
+ {processedRow.map(
+ column =>
+ columns[column.index].display === 'true' && (
- {column}
+ key={column.index}>
+ {column.value}
),
)}
diff --git a/src/components/TableBodyCell.js b/src/components/TableBodyCell.js
index a2faed7b0..ae784330e 100644
--- a/src/components/TableBodyCell.js
+++ b/src/components/TableBodyCell.js
@@ -134,7 +134,7 @@ function TableBodyCell(props) {
className,
)}
{...otherProps}>
- {children}
+ {typeof children === 'function' ? children(dataIndex, rowIndex) : children}
,
];
@@ -150,6 +150,7 @@ function TableBodyCell(props) {
return (
(v != null ? v.toString() : '');
+ const cols = (column.filterOptions && column.filterOptions.fullWidth) === true ? 2 : 1;
return (
-
+
{column.label}
+
v;
+ const cols = (column.filterOptions && column.filterOptions.fullWidth) === true ? 2 : 1;
return (
-
+
{column.label}
+
{display(filterList, this.handleCustomChange, index, column, filterData)}
diff --git a/src/components/TableFilterList.js b/src/components/TableFilterList.js
index 95053fd51..2d230a511 100644
--- a/src/components/TableFilterList.js
+++ b/src/components/TableFilterList.js
@@ -1,126 +1,127 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types';
import React from 'react';
import TableFilterListItem from './TableFilterListItem';
-const defaultFilterListStyles = {
- root: {
- display: 'flex',
- justifyContent: 'left',
- flexWrap: 'wrap',
- margin: '0px 16px 0px 16px',
- },
- chip: {
- margin: '8px 8px 0px 0px',
- },
-};
-
-class TableFilterList extends React.Component {
- static propTypes = {
- /** Data used to filter table against */
- filterList: PropTypes.array.isRequired,
- /** Filter List value renderers */
- filterListRenderers: PropTypes.array.isRequired,
- /** Columns used to describe table */
- columnNames: PropTypes.PropTypes.arrayOf(
- PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.shape({ name: PropTypes.string.isRequired, filterType: PropTypes.string }),
- ]),
- ).isRequired,
- /** Callback to trigger filter update */
- onFilterUpdate: PropTypes.func,
- /** Extend the style applied to components */
- classes: PropTypes.object,
- ItemComponent: PropTypes.any,
- };
-
- static defaultProps = {
- ItemComponent: TableFilterListItem,
- };
-
- render() {
- const {
- classes,
- filterList,
- filterUpdate,
- filterListRenderers,
- columnNames,
- serverSideFilterList,
- customFilterListUpdate,
- ItemComponent,
- } = this.props;
- const { serverSide } = this.props.options;
+const useStyles = makeStyles(
+ () => ({
+ root: {
+ display: 'flex',
+ justifyContent: 'left',
+ flexWrap: 'wrap',
+ margin: '0px 16px 0px 16px',
+ },
+ chip: {
+ margin: '8px 8px 0px 0px',
+ },
+ }),
+ { name: 'MUIDataTableFilterList' },
+);
- const customFilterChip = (customFilterItem, index, customFilterItemIndex, item, isArray) => {
- let type;
+const TableFilterList = ({
+ options,
+ filterList,
+ filterUpdate,
+ filterListRenderers,
+ columnNames,
+ serverSideFilterList,
+ customFilterListUpdate,
+ ItemComponent = TableFilterListItem,
+}) => {
+ const classes = useStyles();
+ const { serverSide } = options;
- // If our custom filter list is an array, we need to check for custom update functions to determine
- // default type. Otherwise we use the supplied type in options.
- if (isArray) type = customFilterListUpdate[index] ? 'custom' : 'chip';
- else type = columnNames[index].filterType;
+ const removeFilter = (index, filterValue, columnName, filterType) => {
+ let removedFilter = filterValue;
+ if (Array.isArray(removedFilter) && removedFilter.length === 0) {
+ removedFilter = filterList[index];
+ }
- return (
-
- );
- };
+ filterUpdate(index, filterValue, columnName, filterType, null, filterList => {
+ if (options.onFilterChipClose) {
+ options.onFilterChipClose(index, removedFilter, filterList);
+ }
+ });
+ };
+ const customFilterChip = (customFilterItem, index, customFilterItemIndex, item, isArray) => {
+ let type;
+ // If our custom filter list is an array, we need to check for custom update functions to determine
+ // default type. Otherwise we use the supplied type in options.
+ if (isArray) {
+ type = customFilterListUpdate[index] ? 'custom' : 'chip';
+ } else {
+ type = columnNames[index].filterType;
+ }
- const filterChip = (index, data, colIndex) => (
+ return (
removeFilter(index, item[customFilterItemIndex] || [], columnNames[index].name, type)}
className={classes.chip}
- itemKey={colIndex}
+ itemKey={customFilterItemIndex}
index={index}
- data={data}
+ data={item}
columnNames={columnNames}
/>
);
+ };
+
+ const filterChip = (index, data, colIndex) => (
+ removeFilter(index, data, columnNames[index].name, 'chip')}
+ className={classes.chip}
+ itemKey={colIndex}
+ index={index}
+ data={data}
+ columnNames={columnNames}
+ />
+ );
- const getFilterList = filterList => {
- return filterList.map((item, index) => {
- if (columnNames[index].filterType === 'custom' && filterList[index].length) {
- const filterListRenderersValue = filterListRenderers[index](item);
+ const getFilterList = filterList => {
+ return filterList.map((item, index) => {
+ if (columnNames[index].filterType === 'custom' && filterList[index].length) {
+ const filterListRenderersValue = filterListRenderers[index](item);
- if (filterListRenderersValue) {
- if (Array.isArray(filterListRenderersValue)) {
- return filterListRenderersValue.map((customFilterItem, customFilterItemIndex) =>
- customFilterChip(customFilterItem, index, customFilterItemIndex, item, true),
- );
- } else {
- return customFilterChip(filterListRenderersValue, index, index, item, false);
- }
+ if (filterListRenderersValue) {
+ if (Array.isArray(filterListRenderersValue)) {
+ return filterListRenderersValue.map((customFilterItem, customFilterItemIndex) =>
+ customFilterChip(customFilterItem, index, customFilterItemIndex, item, true),
+ );
+ } else {
+ return customFilterChip(filterListRenderersValue, index, index, item, false);
}
}
+ }
- return item.map((data, colIndex) => filterChip(index, data, colIndex));
- });
- };
+ return item.map((data, colIndex) => filterChip(index, data, colIndex));
+ });
+ };
- return (
-
- {serverSide && serverSideFilterList ? getFilterList(serverSideFilterList) : getFilterList(filterList)}
-
- );
- }
-}
+ return (
+
+ {serverSide && serverSideFilterList ? getFilterList(serverSideFilterList) : getFilterList(filterList)}
+
+ );
+};
+
+TableFilterList.propTypes = {
+ /** Data used to filter table against */
+ filterList: PropTypes.array.isRequired,
+ /** Filter List value renderers */
+ filterListRenderers: PropTypes.array.isRequired,
+ /** Columns used to describe table */
+ columnNames: PropTypes.arrayOf(
+ PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.shape({ name: PropTypes.string.isRequired, filterType: PropTypes.string }),
+ ]),
+ ).isRequired,
+ /** Callback to trigger filter update */
+ onFilterUpdate: PropTypes.func,
+ ItemComponent: PropTypes.any,
+};
-export default withStyles(defaultFilterListStyles, { name: 'MUIDataTableFilterList' })(TableFilterList);
+export default TableFilterList;
diff --git a/src/components/TableFilterListItem.js b/src/components/TableFilterListItem.js
index e005aa636..0671ff424 100644
--- a/src/components/TableFilterListItem.js
+++ b/src/components/TableFilterListItem.js
@@ -2,18 +2,14 @@ import Chip from '@material-ui/core/Chip';
import PropTypes from 'prop-types';
import React from 'react';
-class TableFilterListItem extends React.PureComponent {
- static propTypes = {
- label: PropTypes.node,
- onDelete: PropTypes.func.isRequired,
- className: PropTypes.string.isRequired,
- };
-
- render() {
- const { label, onDelete, className } = this.props;
-
- return ;
- }
-}
+const TableFilterListItem = ({ label, onDelete, className }) => {
+ return ;
+};
+
+TableFilterListItem.propTypes = {
+ label: PropTypes.node,
+ onDelete: PropTypes.func.isRequired,
+ className: PropTypes.string.isRequired,
+};
export default TableFilterListItem;
diff --git a/src/components/TableHead.js b/src/components/TableHead.js
index 0901ed858..806ff99e7 100644
--- a/src/components/TableHead.js
+++ b/src/components/TableHead.js
@@ -1,137 +1,161 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/core/styles';
import MuiTableHead from '@material-ui/core/TableHead';
import classNames from 'classnames';
-import React from 'react';
-import { findDOMNode } from 'react-dom';
+import React, { useState } from 'react';
import TableHeadCell from './TableHeadCell';
import TableHeadRow from './TableHeadRow';
import TableSelectCell from './TableSelectCell';
-const defaultHeadStyles = theme => ({
- main: {},
- responsiveStacked: {
- [theme.breakpoints.down('sm')]: {
- display: 'none',
+const useStyles = makeStyles(
+ theme => ({
+ main: {},
+ responsiveStacked: {
+ [theme.breakpoints.down('sm')]: {
+ display: 'none',
+ },
},
- },
- responsiveSimple: {
- [theme.breakpoints.down('xs')]: {
- display: 'none',
+ responsiveSimple: {
+ [theme.breakpoints.down('xs')]: {
+ display: 'none',
+ },
},
- },
-});
+ }),
+ { name: 'MUIDataTableHead' },
+);
-class TableHead extends React.Component {
- componentDidMount() {
- this.props.handleHeadUpdateRef(this.handleUpdateCheck);
+const TableHead = ({
+ columnOrder = null,
+ columns,
+ components = {},
+ count,
+ data,
+ draggableHeadCellRefs,
+ expandedRows,
+ options,
+ selectedRows,
+ selectRowUpdate,
+ setCellRef,
+ sortOrder = {},
+ tableRef,
+ timers,
+ toggleAllExpandableRows,
+ toggleSort,
+ updateColumnOrder,
+}) => {
+ const classes = useStyles();
+
+ if (columnOrder === null) {
+ columnOrder = columns ? columns.map((item, idx) => idx) : [];
}
- handleToggleColumn = index => {
- this.props.toggleSort(index);
- };
+ const [dragging, setDragging] = useState(false);
- handleRowSelect = () => {
- this.props.selectRowUpdate('head', null);
+ const handleToggleColumn = index => {
+ toggleSort(index);
};
- render() {
- const {
- classes,
- columns,
- count,
- options,
- data,
- setCellRef,
- selectedRows,
- expandedRows,
- sortOrder = {},
- components = {},
- } = this.props;
+ const handleRowSelect = () => {
+ selectRowUpdate('head', null);
+ };
- const numSelected = (selectedRows && selectedRows.data.length) || 0;
- let isIndeterminate = numSelected > 0 && numSelected < count;
- let isChecked = numSelected > 0 && numSelected >= count;
+ const numSelected = (selectedRows && selectedRows.data.length) || 0;
+ let isIndeterminate = numSelected > 0 && numSelected < count;
+ let isChecked = numSelected > 0 && numSelected >= count;
- // When the disableToolbarSelect option is true, there can be
- // selected items that aren't visible, so we need to be more
- // precise when determining if the head checkbox should be checked.
- if (
- options.disableToolbarSelect === true ||
- options.selectToolbarPlacement === 'none' ||
- options.selectToolbarPlacement === 'above'
- ) {
- if (isChecked) {
- for (let ii = 0; ii < data.length; ii++) {
- if (!selectedRows.lookup[data[ii].dataIndex]) {
- isChecked = false;
- isIndeterminate = true;
- break;
- }
- }
- } else {
- if (numSelected > count) {
+ // When the disableToolbarSelect option is true, there can be
+ // selected items that aren't visible, so we need to be more
+ // precise when determining if the head checkbox should be checked.
+ if (
+ options.disableToolbarSelect === true ||
+ options.selectToolbarPlacement === 'none' ||
+ options.selectToolbarPlacement === 'above'
+ ) {
+ if (isChecked) {
+ for (let ii = 0; ii < data.length; ii++) {
+ if (!selectedRows.lookup[data[ii].dataIndex]) {
+ isChecked = false;
isIndeterminate = true;
+ break;
}
}
+ } else {
+ if (numSelected > count) {
+ isIndeterminate = true;
+ }
}
-
- return (
-
-
- setCellRef(0, findDOMNode(el))}
- onChange={this.handleRowSelect.bind(null)}
- indeterminate={isIndeterminate}
- checked={isChecked}
- isHeaderCell={true}
- expandedRows={expandedRows}
- expandableRowsHeader={options.expandableRowsHeader}
- expandableOn={options.expandableRows}
- selectableOn={options.selectableRows}
- fixedHeader={options.fixedHeader}
- fixedSelectColumn={options.fixedSelectColumn}
- selectableRowsHeader={options.selectableRowsHeader}
- onExpand={this.props.toggleAllExpandableRows}
- isRowSelectable={true}
- />
- {columns.map(
- (column, index) =>
- column.display === 'true' &&
- (column.customHeadRender ? (
- column.customHeadRender({ index, ...column }, this.handleToggleColumn, sortOrder)
- ) : (
- setCellRef(index + 1, findDOMNode(el))}
- sort={column.sort}
- sortDirection={column.name === sortOrder.name ? sortOrder.direction : 'none'}
- toggleSort={this.handleToggleColumn}
- hint={column.hint}
- print={column.print}
- options={options}
- column={column}
- components={components}>
- {column.label}
-
- )),
- )}
-
-
- );
}
-}
-export default withStyles(defaultHeadStyles, { name: 'MUIDataTableHead' })(TableHead);
+ let orderedColumns = columnOrder.map((colIndex, idx) => {
+ return {
+ column: columns[colIndex],
+ index: colIndex,
+ colPos: idx,
+ };
+ });
+
+ return (
+
+
+
+ {orderedColumns.map(
+ ({ column, index, colPos }) =>
+ column.display === 'true' &&
+ (column.customHeadRender ? (
+ column.customHeadRender({ index, ...column }, handleToggleColumn, sortOrder)
+ ) : (
+
+ {column.label}
+
+ )),
+ )}
+
+
+ );
+};
+
+export default TableHead;
diff --git a/src/components/TableHeadCell.js b/src/components/TableHeadCell.js
index c80f2d5c4..a8af19431 100644
--- a/src/components/TableHeadCell.js
+++ b/src/components/TableHeadCell.js
@@ -1,185 +1,278 @@
-import { withStyles } from '@material-ui/core/styles';
-import TableCell from '@material-ui/core/TableCell';
-import TableSortLabel from '@material-ui/core/TableSortLabel';
-import HelpIcon from '@material-ui/icons/Help';
import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
+import HelpIcon from '@material-ui/icons/Help';
import MuiTooltip from '@material-ui/core/Tooltip';
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import TableCell from '@material-ui/core/TableCell';
+import TableSortLabel from '@material-ui/core/TableSortLabel';
+import useColumnDrop from '../hooks/useColumnDrop.js';
+import { makeStyles } from '@material-ui/core/styles';
+import { useDrag } from 'react-dnd';
-const defaultHeadCellStyles = theme => ({
- root: {},
- fixedHeader: {
- position: 'sticky',
- top: '0px',
- zIndex: 100,
- backgroundColor: theme.palette.background.paper,
- },
- tooltip: {
- cursor: 'pointer',
- },
- mypopper: {
- '&[data-x-out-of-boundaries]': {
- display: 'none',
- },
- },
- data: {
- display: 'inline-block',
- },
- sortAction: {
- display: 'flex',
- verticalAlign: 'top',
- cursor: 'pointer',
- },
- sortLabelRoot: {
- height: '10px',
- },
- sortActive: {
- color: theme.palette.text.primary,
- },
- toolButton: {
- display: 'flex',
- outline: 'none',
- cursor: 'pointer',
- },
- hintIconAlone: {
- marginTop: '-3px',
- marginLeft: '3px',
- },
- hintIconWithSortIcon: {
- marginTop: '-3px',
- },
-});
-
-class TableHeadCell extends React.Component {
- static propTypes = {
- /** Extend the style applied to components */
- classes: PropTypes.object,
- /** Options used to describe table */
- options: PropTypes.object.isRequired,
- /** Current sort direction */
- sortDirection: PropTypes.oneOf(['asc', 'desc', 'none']),
- /** Callback to trigger column sort */
- toggleSort: PropTypes.func.isRequired,
- /** Sort enabled / disabled for this column **/
- sort: PropTypes.bool.isRequired,
- /** Hint tooltip text */
- hint: PropTypes.string,
- /** Column displayed in print */
- print: PropTypes.bool.isRequired,
- /** Optional to be used with `textLabels.body.columnHeaderTooltip` */
- column: PropTypes.object,
- /** Injectable component structure **/
- components: PropTypes.object,
- };
+const useStyles = makeStyles(
+ theme => ({
+ root: {},
+ fixedHeader: {
+ position: 'sticky',
+ top: '0px',
+ zIndex: 100,
+ backgroundColor: theme.palette.background.paper,
+ },
+ tooltip: {
+ cursor: 'pointer',
+ },
+ mypopper: {
+ '&[data-x-out-of-boundaries]': {
+ display: 'none',
+ },
+ },
+ data: {
+ display: 'inline-block',
+ },
+ sortAction: {
+ display: 'flex',
+ verticalAlign: 'top',
+ cursor: 'pointer',
+ },
+ dragCursor: {
+ cursor: 'grab',
+ },
+ sortLabelRoot: {
+ height: '10px',
+ },
+ sortActive: {
+ color: theme.palette.text.primary,
+ },
+ toolButton: {
+ display: 'flex',
+ outline: 'none',
+ cursor: 'pointer',
+ },
+ hintIconAlone: {
+ marginTop: '-3px',
+ marginLeft: '3px',
+ },
+ hintIconWithSortIcon: {
+ marginTop: '-3px',
+ },
+ }),
+ { name: 'MUIDataTableHeadCell' },
+);
- handleKeyboardSortinput = e => {
+const TableHeadCell = ({
+ cellHeaderProps = {},
+ children,
+ colPosition,
+ column,
+ columnOrder = [],
+ components = {},
+ draggableHeadCellRefs,
+ draggingHook,
+ hint,
+ index,
+ options,
+ print,
+ setCellRef,
+ sort,
+ sortDirection,
+ tableRef,
+ timers,
+ toggleSort,
+ updateColumnOrder,
+}) => {
+ const [sortTooltipOpen, setSortTooltipOpen] = useState(false);
+ const [hintTooltipOpen, setHintTooltipOpen] = useState(false);
+
+ const classes = useStyles();
+
+ const handleKeyboardSortInput = e => {
if (e.key === 'Enter') {
- this.props.toggleSort(this.props.index);
+ toggleSort(index);
}
return false;
};
- handleSortClick = () => {
- this.props.toggleSort(this.props.index);
+ const handleSortClick = () => {
+ toggleSort(index);
+ };
+
+ const [dragging, setDragging] = draggingHook ? draggingHook : [];
+
+ const { className, ...otherProps } = cellHeaderProps;
+ const Tooltip = components.Tooltip || MuiTooltip;
+ const sortActive = sortDirection !== 'none' && sortDirection !== undefined;
+ const ariaSortDirection = sortDirection === 'none' ? false : sortDirection;
+
+ const sortLabelProps = {
+ classes: { root: classes.sortLabelRoot },
+ active: sortActive,
+ hideSortIcon: true,
+ ...(ariaSortDirection ? { direction: sortDirection } : {}),
};
- render() {
- const {
- children,
- classes,
- options,
- sortDirection,
- sort,
- hint,
- print,
- column,
- cellHeaderProps = {},
- components = {},
- } = this.props;
- const { className, ...otherProps } = cellHeaderProps;
- const Tooltip = components.Tooltip || MuiTooltip;
- const sortActive = sortDirection !== 'none' && sortDirection !== undefined ? true : false;
- const ariaSortDirection = sortDirection === 'none' ? false : sortDirection;
-
- const sortLabelProps = {
- classes: { root: classes.sortLabelRoot },
- active: sortActive,
- hideSortIcon: true,
- ...(ariaSortDirection ? { direction: sortDirection } : {}),
- };
-
- const cellClass = classNames({
- [classes.root]: true,
- [classes.fixedHeader]: options.fixedHeader,
- 'datatables-noprint': !print,
- [className]: className,
- });
-
- return (
-
- {options.sort && sort ? (
-
+ const [{ opacity }, dragRef, preview] = useDrag({
+ item: {
+ type: 'HEADER',
+ colIndex: index,
+ },
+ begin: monitor => {
+ setHintTooltipOpen(false);
+ setSortTooltipOpen(false);
+ setDragging(true);
+ return null;
+ },
+ end: (item, monitor) => {
+ setDragging(false);
+ },
+ collect: monitor => {
+ return {
+ opacity: monitor.isDragging() ? 1 : 0,
+ };
+ },
+ });
+
+ const [drop] = useColumnDrop({
+ drop: (item, mon) => {
+ setSortTooltipOpen(false);
+ setHintTooltipOpen(false);
+ setDragging(false);
+ },
+ index,
+ headCellRefs: draggableHeadCellRefs,
+ updateColumnOrder,
+ columnOrder,
+ transitionTime: options.draggableColumns ? options.draggableColumns.transitionTime : 300,
+ tableRef: tableRef ? tableRef() : null,
+ timers,
+ });
+
+ const isDraggingEnabled = () => {
+ if (!draggingHook) return false;
+
+ return options.draggableColumns && options.draggableColumns.enabled && column.draggable !== false;
+ };
+
+ const cellClass = classNames({
+ [classes.root]: true,
+ [classes.fixedHeader]: options.fixedHeader,
+ 'datatables-noprint': !print,
+ [className]: className,
+ });
+
+ const showHintTooltip = () => {
+ setSortTooltipOpen(false);
+ setHintTooltipOpen(true);
+ };
+
+ const getTooltipTitle = () => {
+ if (dragging) return '';
+ if (!options.textLabels) return '';
+ return options.textLabels.body.columnHeaderTooltip
+ ? options.textLabels.body.columnHeaderTooltip(column)
+ : options.textLabels.body.toolTip;
+ };
+
+ const closeTooltip = () => {
+ setSortTooltipOpen(false);
+ setDragging(true);
+ };
+
+ return (
+ {
+ drop(ref);
+ setCellRef && setCellRef(index + 1, colPosition + 1, ref);
+ }}
+ className={cellClass}
+ scope={'col'}
+ sortDirection={ariaSortDirection}
+ data-colindex={index}
+ onMouseDown={closeTooltip}
+ {...otherProps}>
+ {options.sort && sort ? (
+
+ (dragging ? setSortTooltipOpen(false) : setSortTooltipOpen(true))}
+ onClose={() => setSortTooltipOpen(false)}
+ classes={{
+ tooltip: classes.tooltip,
+ popper: classes.mypopper,
+ }}>
+
+
+ {hint && (
+
+
+
+ )}
+
+ ) : (
+
+ {children}
+ {hint && (
showHintTooltip()}
+ onClose={() => setHintTooltipOpen(false)}
classes={{
tooltip: classes.tooltip,
popper: classes.mypopper,
- }}>
-
+ }}
+ enterDelay={300}>
+
- {hint && (
-
-
-
- )}
-
- ) : (
-
- {children}
- {hint && (
-
-
-
- )}
-
- )}
-
- );
- }
-}
-
-export default withStyles(defaultHeadCellStyles, { name: 'MUIDataTableHeadCell' })(TableHeadCell);
+ )}
+
+ )}
+
+ );
+};
+
+TableHeadCell.propTypes = {
+ /** Options used to describe table */
+ options: PropTypes.object.isRequired,
+ /** Current sort direction */
+ sortDirection: PropTypes.oneOf(['asc', 'desc', 'none']),
+ /** Callback to trigger column sort */
+ toggleSort: PropTypes.func.isRequired,
+ /** Sort enabled / disabled for this column **/
+ sort: PropTypes.bool.isRequired,
+ /** Hint tooltip text */
+ hint: PropTypes.string,
+ /** Column displayed in print */
+ print: PropTypes.bool.isRequired,
+ /** Optional to be used with `textLabels.body.columnHeaderTooltip` */
+ column: PropTypes.object,
+ /** Injectable component structure **/
+ components: PropTypes.object,
+};
+
+export default TableHeadCell;
diff --git a/src/components/TableHeadRow.js b/src/components/TableHeadRow.js
index c8b60e2a0..7f76303c6 100644
--- a/src/components/TableHeadRow.js
+++ b/src/components/TableHeadRow.js
@@ -2,30 +2,30 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import TableRow from '@material-ui/core/TableRow';
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/core/styles';
-const defaultHeadRowStyles = {
- root: {},
-};
+const useStyles = makeStyles(
+ () => ({
+ root: {},
+ }),
+ { name: 'MUIDataTableHeadRow' },
+);
-class TableHeadRow extends React.Component {
- static propTypes = {
- /** Extend the style applied to components */
- classes: PropTypes.object,
- };
+const TableHeadRow = ({ children }) => {
+ const classes = useStyles();
- render() {
- const { classes } = this.props;
+ return (
+
+ {children}
+
+ );
+};
- return (
-
- {this.props.children}
-
- );
- }
-}
+TableHeadRow.propTypes = {
+ children: PropTypes.node,
+};
-export default withStyles(defaultHeadRowStyles, { name: 'MUIDataTableHeadRow' })(TableHeadRow);
+export default TableHeadRow;
diff --git a/src/components/TableResize.js b/src/components/TableResize.js
index 5384bb03a..ec10b9b10 100644
--- a/src/components/TableResize.js
+++ b/src/components/TableResize.js
@@ -77,10 +77,9 @@ class TableResize extends React.Component {
let parentOffsetLeft = getParentOffsetLeft(tableEl);
let finalCells = Object.entries(this.cellsRef);
- finalCells.pop();
+ //finalCells.pop();
finalCells.forEach(([key, item], idx) => {
if (!item) return;
-
let elRect = item.getBoundingClientRect();
let left = elRect.left;
left = (left || 0) - parentOffsetLeft;
@@ -213,6 +212,7 @@ class TableResize extends React.Component {
{Object.entries(resizeCoords).map(([key, val]) => {
return (
false,
selectableRowsHideCheckboxes,
+ setHeadCellRef,
...otherProps
} = this.props;
let fixedHeaderClasses;
@@ -114,6 +115,13 @@ class TableSelectCell extends React.Component {
[classes.hide]: isHeaderCell && !expandableRowsHeader,
});
+ let refProp = {};
+ if (setHeadCellRef) {
+ refProp.ref = el => {
+ setHeadCellRef(0, 0, el);
+ };
+ }
+
const renderCheckBox = () => {
if (isHeaderCell && (selectableOn !== 'multiple' || selectableRowsHeader === false)) {
// only display the header checkbox for multiple selection.
@@ -134,7 +142,7 @@ class TableSelectCell extends React.Component {
};
return (
-
+
{expandableOn && (
diff --git a/src/components/TableToolbar.js b/src/components/TableToolbar.js
index d2d24f7e9..b5b3b86ce 100644
--- a/src/components/TableToolbar.js
+++ b/src/components/TableToolbar.js
@@ -119,9 +119,26 @@ class TableToolbar extends React.Component {
}
handleCSVDownload = () => {
- const { data, displayData, columns, options } = this.props;
- let dataToDownload = cloneDeep(data);
- let columnsToDownload = columns;
+ const { data, displayData, columns, options, colOrder } = this.props;
+ let dataToDownload = []; //cloneDeep(data);
+ let columnsToDownload = [];
+ let columnOrder = Array.isArray(colOrder) ? columnOrder.slice(0) : [];
+
+ if (columnOrder.length === 0) {
+ columnOrder = columns.map((item, idx) => idx);
+ }
+
+ data.forEach(row => {
+ let newRow = { index: row.index, data: [] };
+ columnOrder.forEach(idx => {
+ newRow.data.push(row.data[idx]);
+ });
+ dataToDownload.push(newRow);
+ });
+
+ columnOrder.forEach(idx => {
+ columnsToDownload.push(columns[idx]);
+ });
if (options.downloadOptions && options.downloadOptions.filterOptions) {
// check rows first:
diff --git a/src/hooks/useColumnDrop.js b/src/hooks/useColumnDrop.js
new file mode 100644
index 000000000..b9bef1251
--- /dev/null
+++ b/src/hooks/useColumnDrop.js
@@ -0,0 +1,139 @@
+/*
+ This hook handles the dragging and dropping effects that occur for columns.
+*/
+
+import { useDrop } from 'react-dnd';
+
+const getColModel = (headCellRefs, columnOrder) => {
+ let colModel = [];
+
+ let ii = 0,
+ parentOffsetLeft = 0,
+ offsetParent = headCellRefs[0].offsetParent;
+ while (offsetParent) {
+ parentOffsetLeft = parentOffsetLeft + (offsetParent.offsetLeft || 0);
+ offsetParent = offsetParent.offsetParent;
+ ii++;
+ if (ii > 1000) {
+ console.warn('Table nested within 1000 divs. Maybe an error.');
+ break;
+ }
+ }
+
+ colModel[0] = {
+ left: parentOffsetLeft + headCellRefs[0].offsetLeft,
+ width: headCellRefs[0].offsetWidth,
+ columnIndex: null,
+ ref: headCellRefs[0],
+ };
+
+ columnOrder.forEach((colIdx, idx) => {
+ let col = headCellRefs[colIdx + 1];
+ let cmIndx = colModel.length - 1;
+ colModel.push({
+ left: colModel[cmIndx].left + colModel[cmIndx].width,
+ width: col.offsetWidth,
+ columnIndex: colIdx,
+ ref: col,
+ });
+ });
+
+ return colModel;
+};
+
+const reorderColumns = (prevColumnOrder, columnIndex, newPosition) => {
+ let columnOrder = prevColumnOrder.slice();
+ let srcIndex = columnOrder.indexOf(columnIndex);
+ let targetIndex = columnOrder.indexOf(newPosition);
+
+ if (srcIndex !== -1 && targetIndex !== -1) {
+ let newItem = columnOrder[srcIndex];
+ columnOrder = [...columnOrder.slice(0, srcIndex), ...columnOrder.slice(srcIndex + 1)];
+ columnOrder = [...columnOrder.slice(0, targetIndex), newItem, ...columnOrder.slice(targetIndex)];
+ }
+ return columnOrder;
+};
+
+const useColumnDrop = opts => {
+ const { index, headCellRefs, updateColumnOrder, columnOrder, transitionTime = 300, tableRef, timers } = opts;
+
+ const [{ isOver, canDrop }, drop] = useDrop({
+ accept: 'HEADER',
+ drop: opts.drop,
+ hover: (item, mon) => {
+ let hoverIdx = mon.getItem().colIndex;
+
+ if (hoverIdx !== index) {
+ let reorderedCols = reorderColumns(columnOrder, mon.getItem().colIndex, index);
+ let newColModel = getColModel(headCellRefs, reorderedCols);
+
+ let newX = mon.getClientOffset().x;
+ let modelIdx = -1;
+ for (let ii = 0; ii < newColModel.length; ii++) {
+ if (newX > newColModel[ii].left && newX < newColModel[ii].left + newColModel[ii].width) {
+ modelIdx = newColModel[ii].columnIndex;
+ break;
+ }
+ }
+
+ if (modelIdx === mon.getItem().colIndex) {
+ clearTimeout(timers.columnShift);
+
+ let curColModel = getColModel(headCellRefs, columnOrder);
+
+ let transitions = [];
+ newColModel.forEach(item => {
+ transitions[item.columnIndex] = item.left;
+ });
+ curColModel.forEach(item => {
+ transitions[item.columnIndex] = transitions[item.columnIndex] - item.left;
+ });
+
+ for (let idx = 1; idx < columnOrder.length; idx++) {
+ headCellRefs[idx].style.transition = '280ms';
+ headCellRefs[idx].style.transform = 'translateX(' + transitions[idx - 1] + 'px)';
+ }
+
+ let allElms = [];
+ let dividers = [];
+ for (let ii = 0; ii < columnOrder.length; ii++) {
+ let elms = tableRef ? tableRef.querySelectorAll('[data-colindex="' + ii + '"]') : [];
+ for (let jj = 0; jj < elms.length; jj++) {
+ elms[jj].style.transition = transitionTime + 'ms';
+ elms[jj].style.transform = 'translateX(' + transitions[ii] + 'px)';
+ allElms.push(elms[jj]);
+ }
+
+ let divider = tableRef ? tableRef.querySelectorAll('[data-divider-index="' + (ii + 1) + '"]') : [];
+ for (let jj = 0; jj < divider.length; jj++) {
+ divider[jj].style.transition = transitionTime + 'ms';
+ divider[jj].style.transform = 'translateX(' + transitions[columnOrder[ii]] + 'px)';
+ dividers.push(divider[jj]);
+ }
+ }
+
+ let newColIndex = mon.getItem().colIndex;
+ timers.columnShift = setTimeout(() => {
+ allElms.forEach(item => {
+ item.style.transition = '0s';
+ item.style.transform = 'translateX(0)';
+ });
+ dividers.forEach(item => {
+ item.style.transition = '0s';
+ item.style.transform = 'translateX(0)';
+ });
+ updateColumnOrder(reorderedCols, newColIndex, index);
+ }, transitionTime);
+ }
+ }
+ },
+ collect: mon => ({
+ isOver: !!mon.isOver(),
+ canDrop: !!mon.canDrop(),
+ }),
+ });
+
+ return [drop];
+};
+
+export default useColumnDrop;
diff --git a/test/MUIDataTable.test.js b/test/MUIDataTable.test.js
index 076bb6485..dacba7ec5 100644
--- a/test/MUIDataTable.test.js
+++ b/test/MUIDataTable.test.js
@@ -1938,4 +1938,81 @@ describe(' ', function() {
expect(getCollatorComparator()('testStringA', 'testStringB')).to.equal(-1);
});
});
+
+ it('should correctly execute customBodyRender methods based on filtering and data', () => {
+ let filteredData = [];
+ let customBodyRenderCb = spy();
+ let customBodyRenderLiteCb = spy();
+ let customBodyRenderNoFilterCb = spy();
+ const options = {
+ rowsPerPage: 5,
+ rowsPerPageOptions: [5],
+ };
+
+ const data = [
+ ['a', 'b'],
+ ['a', 'b'],
+ ['a', 'b'],
+ ['a', 'b'],
+ ['a', 'b'],
+
+ ['a', 'b'],
+ ['a', 'b'],
+ ['a', 'b'],
+ ['a', 'b'],
+ ['a', 'b'],
+
+ ['a', 'b'],
+ ['a', 'b'],
+ ['a', 'b'],
+ ['a', 'b'],
+ ['a', 'b'],
+ ];
+
+ const columns = [
+ {
+ name: 'firstName',
+ label: 'First Name',
+ options: {
+ customBodyRender: () => {
+ customBodyRenderCb();
+ return '';
+ },
+ },
+ },
+ {
+ name: 'lastName',
+ label: 'Last Name',
+ options: {
+ customBodyRenderLite: () => {
+ customBodyRenderLiteCb();
+ return '';
+ },
+ },
+ },
+ {
+ name: 'phone',
+ label: 'Phone',
+ options: {
+ filter: false,
+ customBodyRender: () => {
+ customBodyRenderNoFilterCb();
+ return '';
+ },
+ },
+ },
+ ];
+
+ const fullWrapper = mount( );
+ fullWrapper.unmount();
+
+ // lite only gets executed for the 5 entries shown
+ assert.strictEqual(customBodyRenderLiteCb.callCount, 5);
+
+ // regular gets executed 15 times for filtering, and 15 more times for display
+ assert.strictEqual(customBodyRenderCb.callCount, 30);
+
+ // regular with no filtering gets executed 15 times for display
+ assert.strictEqual(customBodyRenderNoFilterCb.callCount, 15);
+ });
});
diff --git a/test/MUIDataTableCustomComponents.test.js b/test/MUIDataTableCustomComponents.test.js
index 66494f3d7..24994784c 100644
--- a/test/MUIDataTableCustomComponents.test.js
+++ b/test/MUIDataTableCustomComponents.test.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { mount, shallow } from 'enzyme';
import { assert } from 'chai';
import MUIDataTable from '../src/MUIDataTable';
import Chip from '@material-ui/core/Chip';
@@ -41,7 +41,7 @@ describe(' with custom components', function() {
});
it('should render a table with custom Chip in TableFilterList', () => {
- const shallowWrapper = shallow(
+ const wrapper = mount(
with custom components', function() {
}}
/>,
);
- const customFilterList = shallowWrapper.dive().find(CustomFilterList);
+ const customFilterList = wrapper.find(CustomFilterList);
assert.lengthOf(customFilterList, 1);
const customChip = customFilterList
- .dive()
- .dive()
- .dive()
.find(CustomChip);
assert.lengthOf(customChip, 1);
});
diff --git a/test/MUIDataTableHead.test.js b/test/MUIDataTableHead.test.js
index bdb486846..a8a74aa92 100644
--- a/test/MUIDataTableHead.test.js
+++ b/test/MUIDataTableHead.test.js
@@ -6,6 +6,8 @@ import TableHead from '../src/components/TableHead';
import TableHeadCell from '../src/components/TableHeadCell';
import Checkbox from '@material-ui/core/Checkbox';
import Tooltip from '@material-ui/core/Tooltip';
+import { DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
describe(' ', function() {
let columns;
@@ -13,14 +15,14 @@ describe(' ', function() {
before(() => {
columns = [
- { name: 'First Name', label: 'First Name', display: 'true', sort: null },
+ { name: 'First Name', label: 'First Name', display: 'true', sort: true },
{ name: 'Company', label: 'Company', display: 'true', sort: null },
{ name: 'City', label: 'City Label', display: 'true', sort: null },
{
name: 'State',
label: 'State',
display: 'true',
- options: { fixedHeaderOptions: { xAxis: true, yAxis: true } },
+ options: { fixedHeaderOptions: { xAxis: true, yAxis: true }, selectableRows: 'multiple' },
customHeadRender: columnMeta => {columnMeta.name + 's'} ,
sort: null,
},
@@ -32,16 +34,19 @@ describe(' ', function() {
it('should render a table head', () => {
const options = {};
const toggleSort = () => {};
-
+console.dir(columns);
const mountWrapper = mount(
- {}}
- handleHeadUpdateRef={handleHeadUpdateRef}
- toggleSort={toggleSort}
- />,
+
+ {}}
+ handleHeadUpdateRef={handleHeadUpdateRef}
+ toggleSort={toggleSort}
+ />
+ ,
);
+ console.log(mountWrapper.html());
const actualResult = mountWrapper.find(TableHeadCell);
assert.strictEqual(actualResult.length, 4);
});
@@ -51,13 +56,15 @@ describe(' ', function() {
const toggleSort = () => {};
const mountWrapper = mount(
- {}}
- handleHeadUpdateRef={handleHeadUpdateRef}
- toggleSort={toggleSort}
- />,
+
+ {}}
+ handleHeadUpdateRef={handleHeadUpdateRef}
+ toggleSort={toggleSort}
+ />
+ ,
);
const labels = mountWrapper.find(TableHeadCell).map(n => n.text());
assert.deepEqual(labels, ['First Name', 'Company', 'City Label', 'States']);
@@ -69,13 +76,15 @@ describe(' ', function() {
const newColumns = columns.map(column => ({ ...column, display: false }));
const mountWrapper = mount(
- {}}
- handleHeadUpdateRef={handleHeadUpdateRef}
- toggleSort={toggleSort}
- />,
+
+ {}}
+ handleHeadUpdateRef={handleHeadUpdateRef}
+ toggleSort={toggleSort}
+ />
+ ,
);
const actualResult = mountWrapper.find(TableHeadCell);
assert.strictEqual(actualResult.length, 0);
@@ -85,42 +94,43 @@ describe(' ', function() {
const options = { sort: true };
const toggleSort = spy();
- const shallowWrapper = shallow(
- {}}
- handleHeadUpdateRef={handleHeadUpdateRef}
- toggleSort={toggleSort}
- />,
- ).dive();
+ const wrapper = mount(
+
+ {}}
+ handleHeadUpdateRef={handleHeadUpdateRef}
+ toggleSort={toggleSort}
+ />
+ ,
+ );
- const instance = shallowWrapper.instance();
- instance.handleToggleColumn(2);
- shallowWrapper.update();
+ const instance = wrapper.find('th span').at(0);
+ instance.simulate('click');
assert.strictEqual(toggleSort.callCount, 1);
});
it('should trigger selectRowUpdate prop callback and selectChecked state update when calling method handleRowSelect', () => {
- const options = { sort: true, selectableRows: true };
+ const options = { sort: true, selectableRows: 'multiple' };
const rowSelectUpdate = spy();
- const shallowWrapper = shallow(
- {}}
- handleHeadUpdateRef={handleHeadUpdateRef}
- selectRowUpdate={rowSelectUpdate}
- />,
- ).dive();
-
- const instance = shallowWrapper.instance();
- instance.handleRowSelect(2);
- shallowWrapper.update();
-
- let state = shallowWrapper.state();
+ const wrapper = mount(
+
+ {}}
+ handleHeadUpdateRef={handleHeadUpdateRef}
+ selectRowUpdate={rowSelectUpdate}
+ />
+ ,
+ );
+
+ const instance = wrapper.find('input[type="checkbox"]');
+ instance.simulate('change', { target: { value: 'checked' } });
+
assert.strictEqual(rowSelectUpdate.callCount, 1);
});
@@ -128,7 +138,14 @@ describe(' ', function() {
const options = { selectableRows: 'multiple' };
const mountWrapper = mount(
- {}} handleHeadUpdateRef={handleHeadUpdateRef} />,
+
+ {}}
+ handleHeadUpdateRef={handleHeadUpdateRef}
+ />
+ ,
);
const actualResult = mountWrapper.find(Checkbox);
@@ -139,7 +156,14 @@ describe(' ', function() {
const options = { selectableRows: 'single' };
const mountWrapper = mount(
- {}} handleHeadUpdateRef={handleHeadUpdateRef} />,
+
+ {}}
+ handleHeadUpdateRef={handleHeadUpdateRef}
+ />
+ ,
);
const actualResult = mountWrapper.find(Checkbox);
@@ -150,7 +174,14 @@ describe(' ', function() {
const options = { selectableRows: 'none' };
const mountWrapper = mount(
- {}} handleHeadUpdateRef={handleHeadUpdateRef} />,
+
+ {}}
+ handleHeadUpdateRef={handleHeadUpdateRef}
+ />
+ ,
);
const actualResult = mountWrapper.find(Checkbox);
diff --git a/test/MUIDataTableHeadCell.test.js b/test/MUIDataTableHeadCell.test.js
index 340f7de96..2d9889ee3 100644
--- a/test/MUIDataTableHeadCell.test.js
+++ b/test/MUIDataTableHeadCell.test.js
@@ -7,6 +7,8 @@ import TableHeadCell from '../src/components/TableHeadCell';
import TableCell from '@material-ui/core/TableCell';
import TableSortLabel from '@material-ui/core/TableSortLabel';
import HelpIcon from '@material-ui/icons/Help';
+import { DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
describe(' ', function() {
let classes;
@@ -25,15 +27,17 @@ describe(' ', function() {
const toggleExpandRow = () => {};
const mountWrapper = mount(
-
- some content
- ,
+
+
+ some content
+
+ ,
);
const props = mountWrapper.find(TableCell).props();
@@ -48,13 +52,15 @@ describe(' ', function() {
const options = { sort: true, textLabels: getTextLabels() };
const toggleSort = () => {};
- const shallowWrapper = shallow(
-
- some content
- ,
- ).dive();
+ const wrapper = mount(
+
+
+ some content
+
+ ,
+ );
- const actualResult = shallowWrapper.find(TableSortLabel);
+ const actualResult = wrapper.find(TableSortLabel);
assert.strictEqual(actualResult.length, 1);
});
@@ -63,9 +69,11 @@ describe(' ', function() {
const toggleSort = () => {};
const shallowWrapper = shallow(
-
- some content
- ,
+
+
+ some content
+
+ ,
);
const actualResult = shallowWrapper.find(TableSortLabel);
@@ -75,13 +83,15 @@ describe(' ', function() {
it('should render a table help icon when hint provided', () => {
const options = { sort: true, textLabels: getTextLabels() };
- const shallowWrapper = shallow(
-
- some content
- ,
- ).dive();
+ const wrapper = mount(
+
+
+ some content
+
+ ,
+ );
- const actualResult = shallowWrapper.find(HelpIcon);
+ const actualResult = wrapper.find(HelpIcon);
assert.strictEqual(actualResult.length, 1);
});
@@ -89,9 +99,11 @@ describe(' ', function() {
const options = { sort: true, textLabels: getTextLabels() };
const shallowWrapper = shallow(
-
- some content
- ,
+
+
+ some content
+
+ ,
).dive();
const actualResult = shallowWrapper.find(HelpIcon);
@@ -102,16 +114,26 @@ describe(' ', function() {
const options = { sort: true, textLabels: getTextLabels() };
const toggleSort = spy();
- const shallowWrapper = shallow(
-
- some content
- ,
- ).dive();
-
- const instance = shallowWrapper.instance();
+ const wrapper = mount(
+
+
+ some content
+
+ ,
+ );
+ const instance = wrapper
+ .find('td')
+ .at(0)
+ .childAt(0);
const event = { target: { value: 'All' } };
- instance.handleSortClick();
+ instance.simulate('click');
assert.strictEqual(toggleSort.callCount, 1);
});
});
diff --git a/test/TableResize.test.js b/test/TableResize.test.js
index b1a09d986..74e471099 100644
--- a/test/TableResize.test.js
+++ b/test/TableResize.test.js
@@ -46,6 +46,6 @@ describe(' ', function() {
colCoordCount++;
}
- assert.strictEqual(colCoordCount, 4);
+ assert.strictEqual(colCoordCount, 5);
});
});