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:

+|**`draggableColumns`**|object|{}|An object of options describing how dragging columns should work. The options are:

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:

`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.

|**`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 ( ); @@ -116,7 +116,7 @@ class Example extends React.Component { filter: false, sort: false, empty: true, - customBodyRender: (value, tableMeta, updateValue) => { + customBodyRenderLite: (dataIndex) => { return ( } @@ -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} + {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, + }}> +
+
+ {children} +
+
+ +
+
+
+ {hint && ( + + + + )} +
+ ) : ( +
+ {children} + {hint && ( showHintTooltip()} + onClose={() => setHintTooltipOpen(false)} classes={{ tooltip: classes.tooltip, popper: classes.mypopper, - }}> -
-
- {children} -
-
- -
-
+ }} + 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 (