diff --git a/__tests__/demo/demo-components/index.js b/__tests__/demo/demo-components/index.js
index f8ade78a..de42d905 100644
--- a/__tests__/demo/demo-components/index.js
+++ b/__tests__/demo/demo-components/index.js
@@ -987,6 +987,20 @@ export function DefaultOrderIssue(props) {
birthYear: 2017,
birthCity: 34,
id: 1
+ },
+ {
+ name: 'Mehmet',
+ surname: 'Terot',
+ birthYear: 1997,
+ birthCity: 63,
+ id: 3
+ },
+ {
+ name: 'Mehmet',
+ surname: 'Terot',
+ birthYear: 2000,
+ birthCity: 34,
+ id: 4
}
]}
options={{
@@ -1253,3 +1267,93 @@ export function FixedColumnWithEdit() {
/>
);
}
+
+export function TableMultiSorting(props) {
+ const global_cols = [
+ {
+ title: 'Number',
+ field: 'number',
+ minWidth: 140,
+ maxWidth: 400
+ },
+ {
+ title: 'Title',
+ field: 'title',
+ minWidth: 140,
+ maxWidth: 400,
+ sorting: true,
+ defaultSort: 'desc'
+ },
+ {
+ title: 'Name',
+ field: 'name',
+ minWidth: 140,
+ maxWidth: 400,
+ sorting: true,
+ defaultSort: 'desc'
+ },
+ {
+ title: 'Last Name',
+ field: 'lastName',
+ minWidth: 140,
+ maxWidth: 400,
+ sorting: true,
+ defaultSort: 'asc'
+ }
+ ];
+
+ const global_data1 = [
+ {
+ number: 1,
+ title: 'Developer',
+ name: 'Mehmet',
+ lastName: 'Baran',
+ id: '1231'
+ },
+ {
+ number: 22,
+ title: 'Developer',
+ name: 'Pratik',
+ lastName: 'N',
+ id: '1234'
+ },
+ {
+ number: 25,
+ title: 'Human Resources',
+ name: 'Juan',
+ lastName: 'Lopez',
+ id: '1235'
+ },
+ {
+ number: 3,
+ title: 'Consultant',
+ name: 'Raul',
+ lastName: 'Barak',
+ id: '1236'
+ }
+ ];
+
+ const orderCollection = [
+ { orderBy: 1, orderDirection: 'asc', sortOrder: 1 },
+ { orderBy: 2, orderDirection: 'desc', sortOrder: 2 }
+ ];
+
+ const onOrderCollectionChange = (orderByCollection) => {
+ console.log('onOrderCollectionChange ===>', orderByCollection);
+ };
+
+ return (
+
+ );
+}
diff --git a/__tests__/demo/demo.js b/__tests__/demo/demo.js
index 08b408c6..e8157461 100644
--- a/__tests__/demo/demo.js
+++ b/__tests__/demo/demo.js
@@ -45,7 +45,8 @@ import {
TreeData,
TableWithSummary,
TableWithNumberOfPagesAround,
- FixedColumnWithEdit
+ FixedColumnWithEdit,
+ TableMultiSorting
} from './demo-components';
import { I1353, I1941, I122 } from './demo-components/RemoteData';
import { Table, TableCell, TableRow, Paper } from '@material-ui/core';
@@ -60,6 +61,9 @@ render(
DetailPanelRemounting
+ Multi Sorting
+
+
Switcher
diff --git a/__tests__/multiColumnSort.test.js b/__tests__/multiColumnSort.test.js
new file mode 100644
index 00000000..fbb3745e
--- /dev/null
+++ b/__tests__/multiColumnSort.test.js
@@ -0,0 +1,193 @@
+import '@testing-library/jest-dom';
+import { fireEvent, render } from '@testing-library/react';
+import * as React from 'react';
+import MaterialTable from '../src';
+
+const columns = [
+ {
+ title: 'Number',
+ field: 'number',
+ minWidth: 140,
+ maxWidth: 400
+ },
+ {
+ title: 'Title',
+ field: 'title',
+ minWidth: 140,
+ maxWidth: 400,
+ sorting: true
+ },
+ {
+ title: 'Name',
+ field: 'name',
+ minWidth: 140,
+ maxWidth: 400,
+ sorting: true
+ },
+ {
+ title: 'Last Name',
+ field: 'lastName',
+ minWidth: 140,
+ maxWidth: 400,
+ sorting: true
+ }
+];
+
+const data = [
+ {
+ number: 1,
+ title: 'Developer',
+ name: 'Mehmet',
+ lastName: 'Baran',
+ id: '1231'
+ },
+ {
+ number: 22,
+ title: 'Developer',
+ name: 'Pratik',
+ lastName: 'N',
+ id: '1234'
+ },
+ {
+ number: 25,
+ title: 'Human Resources',
+ name: 'Juan',
+ lastName: 'Lopez',
+ id: '1235'
+ },
+ {
+ number: 3,
+ title: 'Consultant',
+ name: 'Raul',
+ lastName: 'Barak',
+ id: '1236'
+ }
+];
+
+describe('Multi Column Sort', () => {
+ let initialOrderCollection = [];
+ let onOrderCollectionChangeSpy;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ onOrderCollectionChangeSpy = jest.fn();
+ initialOrderCollection = [
+ {
+ orderBy: 1,
+ orderDirection: 'asc',
+ sortOrder: 1
+ },
+ {
+ orderBy: 2,
+ orderDirection: 'desc',
+ sortOrder: 2
+ }
+ ];
+ });
+
+ test('should update table by multi column', () => {
+ const { queryAllByTestId } = render(
+
+ );
+
+ const numberColumn = queryAllByTestId('mtableheader-sortlabel')[0];
+ fireEvent.click(numberColumn);
+
+ expect(onOrderCollectionChangeSpy).toHaveBeenCalledWith([
+ { sortOrder: 1, orderBy: 0, orderDirection: 'asc' }
+ ]);
+
+ const titleColumn = queryAllByTestId('mtableheader-sortlabel')[1];
+ fireEvent.click(titleColumn);
+
+ expect(onOrderCollectionChangeSpy).toHaveBeenCalledWith([
+ { sortOrder: 1, orderBy: 0, orderDirection: 'asc' },
+ { sortOrder: 2, orderBy: 1, orderDirection: 'asc' }
+ ]);
+ });
+
+ test('should update table by multi column and replace first if reach the maximum order columns', () => {
+ const { queryAllByTestId } = render(
+
+ );
+
+ const numberColumn = queryAllByTestId('mtableheader-sortlabel')[0];
+ fireEvent.click(numberColumn);
+
+ expect(onOrderCollectionChangeSpy).toHaveBeenCalledWith([
+ { sortOrder: 1, orderBy: 0, orderDirection: 'asc' }
+ ]);
+
+ fireEvent.click(queryAllByTestId('mtableheader-sortlabel')[1]);
+ fireEvent.click(queryAllByTestId('mtableheader-sortlabel')[2]);
+ fireEvent.click(queryAllByTestId('mtableheader-sortlabel')[3]);
+
+ expect(onOrderCollectionChangeSpy).toHaveBeenCalledWith([
+ { sortOrder: 1, orderBy: 1, orderDirection: 'asc' },
+ { sortOrder: 2, orderBy: 2, orderDirection: 'asc' },
+ { sortOrder: 3, orderBy: 3, orderDirection: 'asc' }
+ ]);
+ });
+
+ test('should order desc when secon click', () => {
+ const { queryAllByTestId } = render(
+
+ );
+
+ const numberColumn = queryAllByTestId('mtableheader-sortlabel')[0];
+ fireEvent.click(numberColumn);
+ fireEvent.click(numberColumn);
+
+ expect(onOrderCollectionChangeSpy).toHaveBeenCalledWith([
+ { sortOrder: 1, orderBy: 0, orderDirection: 'desc' }
+ ]);
+ });
+
+ test('should have being initialized by defaultOrderByCollection', () => {
+ const { queryAllByTestId } = render(
+
+ );
+
+ const numberColumn = queryAllByTestId('mtableheader-sortlabel')[0];
+ fireEvent.click(numberColumn);
+
+ expect(onOrderCollectionChangeSpy).toHaveBeenCalledWith([
+ { sortOrder: 1, orderBy: 1, orderDirection: 'asc' },
+ { sortOrder: 2, orderBy: 2, orderDirection: 'desc' },
+ { sortOrder: 3, orderBy: 0, orderDirection: 'asc' }
+ ]);
+ });
+});
diff --git a/__tests__/pre.build.test.js b/__tests__/pre.build.test.js
index 78da0e2b..3c625374 100644
--- a/__tests__/pre.build.test.js
+++ b/__tests__/pre.build.test.js
@@ -39,6 +39,7 @@ describe('Render Table : Pre Build', () => {
expect(screen.getAllByRole('table')).toHaveLength(2);
});
});
+
// Render table with data
describe('when attempting to render a table with data', () => {
it('renders without crashing', () => {
@@ -78,6 +79,7 @@ describe('Render Table : Pre Build', () => {
name: /5 rows First Page Previous Page 1-5 of 99 Next Page Last Page/i
});
});
+
it('navigates between the pages', () => {
const data = makeData();
render();
@@ -125,6 +127,7 @@ describe('Render Table : Pre Build', () => {
});
expect(screen.getAllByRole('row')).toHaveLength(8);
});
+
it('filters data by search input', async () => {
const data = makeData();
render();
diff --git a/package-lock.json b/package-lock.json
index da1314ae..8abc6046 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1224,9 +1224,9 @@
}
},
"@discoveryjs/json-ext": {
- "version": "0.5.6",
- "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz",
- "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==",
+ "version": "0.5.7",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
+ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
"dev": true
},
"@emotion/hash": {
@@ -2913,15 +2913,15 @@
}
},
"@webpack-cli/configtest": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz",
- "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==",
+ "version": "1.2.0",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
+ "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==",
"dev": true
},
"@webpack-cli/info": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz",
- "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==",
+ "version": "1.5.0",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/@webpack-cli/info/-/info-1.5.0.tgz",
+ "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==",
"dev": true,
"requires": {
"envinfo": "^7.7.3"
@@ -4288,7 +4288,7 @@
},
"clone-deep": {
"version": "4.0.1",
- "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/clone-deep/-/clone-deep-4.0.1.tgz",
"integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
"dev": true,
"requires": {
@@ -4340,9 +4340,9 @@
"dev": true
},
"colorette": {
- "version": "2.0.16",
- "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
- "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==",
+ "version": "2.0.19",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/colorette/-/colorette-2.0.19.tgz",
+ "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==",
"dev": true
},
"combined-stream": {
@@ -5095,7 +5095,7 @@
},
"envinfo": {
"version": "7.8.1",
- "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/envinfo/-/envinfo-7.8.1.tgz",
"integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==",
"dev": true
},
@@ -6157,9 +6157,9 @@
"dev": true
},
"fastest-levenshtein": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz",
- "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==",
+ "version": "1.0.16",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
+ "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
"dev": true
},
"fastq": {
@@ -6895,12 +6895,6 @@
}
}
},
- "human-signals": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
- "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
- "dev": true
- },
"husky": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/husky/-/husky-1.2.0.tgz",
@@ -7103,7 +7097,7 @@
},
"interpret": {
"version": "2.2.0",
- "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/interpret/-/interpret-2.2.0.tgz",
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
"dev": true
},
@@ -10451,7 +10445,7 @@
},
"rechoir": {
"version": "0.7.1",
- "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/rechoir/-/rechoir-0.7.1.tgz",
"integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
"dev": true,
"requires": {
@@ -11029,7 +11023,7 @@
},
"shallow-clone": {
"version": "3.0.1",
- "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/shallow-clone/-/shallow-clone-3.0.1.tgz",
"integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
"dev": true,
"requires": {
@@ -12546,18 +12540,18 @@
}
},
"webpack-cli": {
- "version": "4.9.2",
- "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz",
- "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==",
+ "version": "4.10.0",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/webpack-cli/-/webpack-cli-4.10.0.tgz",
+ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
"dev": true,
"requires": {
"@discoveryjs/json-ext": "^0.5.0",
- "@webpack-cli/configtest": "^1.1.1",
- "@webpack-cli/info": "^1.4.1",
- "@webpack-cli/serve": "^1.6.1",
+ "@webpack-cli/configtest": "^1.2.0",
+ "@webpack-cli/info": "^1.5.0",
+ "@webpack-cli/serve": "^1.7.0",
"colorette": "^2.0.14",
"commander": "^7.0.0",
- "execa": "^5.0.0",
+ "cross-spawn": "^7.0.3",
"fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2",
"interpret": "^2.2.0",
@@ -12565,49 +12559,17 @@
"webpack-merge": "^5.7.3"
},
"dependencies": {
+ "@webpack-cli/serve": {
+ "version": "1.7.0",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/@webpack-cli/serve/-/serve-1.7.0.tgz",
+ "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==",
+ "dev": true
+ },
"commander": {
"version": "7.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"dev": true
- },
- "execa": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
- "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
- "dev": true,
- "requires": {
- "cross-spawn": "^7.0.3",
- "get-stream": "^6.0.0",
- "human-signals": "^2.1.0",
- "is-stream": "^2.0.0",
- "merge-stream": "^2.0.0",
- "npm-run-path": "^4.0.1",
- "onetime": "^5.1.2",
- "signal-exit": "^3.0.3",
- "strip-final-newline": "^2.0.0"
- }
- },
- "get-stream": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
- "dev": true
- },
- "is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
- "dev": true
- },
- "npm-run-path": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
- "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
- "dev": true,
- "requires": {
- "path-key": "^3.0.0"
- }
}
}
},
@@ -12859,7 +12821,7 @@
},
"webpack-merge": {
"version": "5.8.0",
- "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/webpack-merge/-/webpack-merge-5.8.0.tgz",
"integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==",
"dev": true,
"requires": {
@@ -12943,7 +12905,7 @@
},
"wildcard": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
+ "resolved": "https://jdasoftware.jfrog.io/jdasoftware/api/npm/npm/wildcard/-/wildcard-2.0.0.tgz",
"integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
"dev": true
},
diff --git a/package.json b/package.json
index 6c49ba35..5e806d0b 100644
--- a/package.json
+++ b/package.json
@@ -102,7 +102,7 @@
"typescript": "^4.1.3",
"webpack": "^5.11.0",
"webpack-bundle-analyzer": "^4.3.0",
- "webpack-cli": "^4.9.2",
+ "webpack-cli": "^4.10.0",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
diff --git a/src/components/MTableHeader/index.js b/src/components/MTableHeader/index.js
index 437db7fb..0c21f1ea 100644
--- a/src/components/MTableHeader/index.js
+++ b/src/components/MTableHeader/index.js
@@ -236,17 +236,20 @@ export function MTableHeader({ onColumnResized, columns, ...props }) {
}
}
>
- {columnDef.sorting !== false && options.sorting ? (
+ {columnDef.sorting !== false &&
+ options.sorting &&
+ props.allowSorting ? (
{columnDef.title}
@@ -257,18 +260,23 @@ export function MTableHeader({ onColumnResized, columns, ...props }) {
)}
);
- } else if (columnDef.sorting !== false && options.sorting) {
+ } else if (
+ columnDef.sorting !== false &&
+ options.sorting &&
+ !props.allowSorting
+ ) {
content = (
{columnDef.title}
@@ -460,60 +468,81 @@ const computeNewOrderDirection = (
function RenderSortButton({
columnDef,
- orderBy,
keepSortDirectionOnColumnSwitch,
- orderDirection,
icon,
thirdSortClick,
onOrderChange,
- children
+ children,
+ orderByCollection,
+ showColumnSortOrder,
+ sortOrderIndicatorStyle
}) {
- const active = orderBy === columnDef.tableData.id;
+ const activeColumn = orderByCollection.find(
+ ({ orderBy }) => orderBy === columnDef.tableData.id
+ );
+
// If current sorted column or prop asked to
// maintain sort order when switching sorted column,
// follow computed order direction if defined
// else default direction is asc
const direction =
- active || keepSortDirectionOnColumnSwitch ? orderDirection || 'asc' : 'asc';
- let ariaSort = 'none';
+ activeColumn || keepSortDirectionOnColumnSwitch
+ ? (activeColumn && activeColumn.orderDirection) || 'asc'
+ : 'asc';
- if (active && direction === 'asc') {
+ let ariaSort = 'none';
+ if (activeColumn && direction === 'asc') {
ariaSort = columnDef.ariaSortAsc ? columnDef.ariaSortAsc : 'Ascendant';
- }
- if (active && direction === 'desc') {
+ } else if (activeColumn && direction === 'desc') {
ariaSort = columnDef.ariaSortDesc ? columnDef.ariaSortDesc : 'Descendant';
}
+ const orderBy = activeColumn && activeColumn.orderBy;
+
return (
- {
- const newOrderDirection = computeNewOrderDirection(
- orderBy,
- orderDirection,
- columnDef,
- thirdSortClick,
- keepSortDirectionOnColumnSwitch
- );
- onOrderChange(columnDef.tableData.id, newOrderDirection);
- }}
- >
- {children}
-
+ <>
+ {
+ const newOrderDirection = computeNewOrderDirection(
+ orderBy,
+ direction,
+ columnDef,
+ thirdSortClick,
+ keepSortDirectionOnColumnSwitch
+ );
+ onOrderChange(
+ columnDef.tableData.id,
+ newOrderDirection,
+ activeColumn && activeColumn.sortOrder
+ );
+ }}
+ >
+ {children}
+
+ {showColumnSortOrder && activeColumn && (
+
+ {activeColumn.sortOrder}
+
+ )}
+ >
);
}
MTableHeader.defaultProps = {
dataCount: 0,
selectedCount: 0,
- orderBy: undefined,
- orderDirection: 'asc'
+ orderByCollection: [],
+ allowSorting: true
};
MTableHeader.propTypes = {
@@ -523,10 +552,11 @@ MTableHeader.propTypes = {
selectedCount: PropTypes.number,
onAllSelected: PropTypes.func,
onOrderChange: PropTypes.func,
- orderBy: PropTypes.number,
- orderDirection: PropTypes.string,
showActionsColumn: PropTypes.bool,
- tooltip: PropTypes.string
+ orderByCollection: PropTypes.array,
+ showColumnSortOrder: PropTypes.bool,
+ tooltip: PropTypes.string,
+ allowSorting: PropTypes.bool
};
export const styles = (theme) => ({
diff --git a/src/defaults/props.options.js b/src/defaults/props.options.js
index 15ea08e8..7dc3e919 100644
--- a/src/defaults/props.options.js
+++ b/src/defaults/props.options.js
@@ -43,6 +43,9 @@ export default {
selection: false,
selectionProps: {},
sorting: true,
+ maxColumnSort: 1,
+ defaultOrderByCollection: [],
+ showColumnSortOrder: false,
keepSortDirectionOnColumnSwitch: true,
toolbar: true,
defaultExpanded: false,
diff --git a/src/index.js b/src/index.js
index cb82f92c..f8698261 100644
--- a/src/index.js
+++ b/src/index.js
@@ -56,3 +56,5 @@ export {
MTableSteppedPagination,
MTableToolbar
} from './components';
+
+export { ALL_COLUMNS } from './utils/constants';
diff --git a/src/material-table.js b/src/material-table.js
index f73bb793..37ab5523 100644
--- a/src/material-table.js
+++ b/src/material-table.js
@@ -19,6 +19,7 @@ import {
export default class MaterialTable extends React.Component {
dataManager = new DataManager();
checkedForFunctions = false;
+
constructor(props) {
super(props);
@@ -42,10 +43,10 @@ export default class MaterialTable extends React.Component {
(a) => a.tableData.id === renderState.orderBy
),
orderDirection: renderState.orderDirection,
+ orderByCollection: renderState.orderByCollection,
page: 0,
pageSize: calculatedProps.options.pageSize,
search: renderState.searchText,
-
totalCount: 0
},
showAddRow: false,
@@ -72,6 +73,7 @@ export default class MaterialTable extends React.Component {
page: this.props.options.initialPage || 0
});
}
+
/**
* THIS WILL NEED TO BE REMOVED EVENTUALLY.
* Warn consumer of renamed prop.
@@ -81,6 +83,16 @@ export default class MaterialTable extends React.Component {
'Property `onDoubleRowClick` has been renamed to `onRowDoubleClick`'
);
}
+
+ /**
+ * THIS WILL NEED TO BE REMOVED EVENTUALLY.
+ * Warn consumer of deprecated prop.
+ */
+ if (this.props.sorting !== undefined) {
+ console.error(
+ 'Property `sorting` has been deprecated, please start using `maxColumnSort` instead'
+ );
+ }
}
);
}
@@ -113,6 +125,10 @@ export default class MaterialTable extends React.Component {
this.dataManager.setDefaultExpanded(props.options.defaultExpanded);
this.dataManager.changeRowEditing();
+ const { grouping, maxColumnSort } = props.options;
+ this.dataManager.setMaxColumnSort(grouping ? 1 : maxColumnSort);
+ this.dataManager.setOrderByCollection();
+
if (this.isRemoteData(props)) {
this.dataManager.changeApplySearch(false);
this.dataManager.changeApplyFilters(false);
@@ -124,47 +140,49 @@ export default class MaterialTable extends React.Component {
this.dataManager.setData(props.data, props.options.idSynonym);
}
- let defaultSortColumnIndex = -1;
- let defaultSortDirection = '';
- let prevSortColumnIndex = -1;
- let prevSortDirection = '';
- if (props && props.options.sorting !== false) {
- defaultSortColumnIndex = props.columns.findIndex(
- (a) => a.defaultSort && a.sorting !== false
+ const { defaultOrderByCollection } = props.options;
+ let defaultCollectionSort = [];
+ let prevCollectionSort = [];
+
+ if (defaultOrderByCollection && defaultOrderByCollection.length) {
+ defaultCollectionSort = [...defaultOrderByCollection].slice(
+ 0,
+ maxColumnSort
);
- defaultSortDirection =
- defaultSortColumnIndex > -1
- ? props.columns[defaultSortColumnIndex].defaultSort
- : '';
- }
- if (prevColumns) {
- prevSortColumnIndex = prevColumns.findIndex(
- (a) => a.defaultSort && a.sorting !== false
+ } else {
+ const defaultSorts = getDefaultCollectionSort(
+ props.columns,
+ prevColumns,
+ this.dataManager.maxColumnSort
);
- prevSortDirection =
- prevSortColumnIndex > -1 && props.columns[prevSortColumnIndex]
- ? props.columns[prevSortColumnIndex].defaultSort
- : '';
+ defaultCollectionSort = [...defaultSorts[0]].slice(0, maxColumnSort);
+ prevCollectionSort = [...defaultSorts[1]];
}
+ const defaultSort = JSON.stringify(defaultCollectionSort);
+ const prevSort = JSON.stringify(prevCollectionSort);
+ const currentSort = JSON.stringify(this.dataManager.orderByCollection);
// If the default sorting changed and differs from the current default sorting, it will trigger a new sorting
const shouldReorder =
isInit ||
(!this.isRemoteData() &&
// Only if a defaultSortingDirection is passed, it will evaluate for changes
- defaultSortDirection &&
+ defaultCollectionSort.length &&
// Default sorting has changed
- (defaultSortColumnIndex !== prevSortColumnIndex ||
- defaultSortDirection !== prevSortDirection) &&
+ defaultSort !== prevSort &&
// Default sorting differs from current sorting
- (defaultSortColumnIndex !== this.dataManager.orderBy ||
- defaultSortDirection !== this.dataManager.orderDirection));
+ defaultSort !== currentSort);
- shouldReorder &&
- this.dataManager.changeOrder(
- defaultSortColumnIndex,
- defaultSortDirection
+ if (
+ shouldReorder &&
+ defaultCollectionSort.length > 0 &&
+ maxColumnSort > 0
+ ) {
+ defaultCollectionSort.forEach(({ orderBy, orderDirection, sortOrder }) =>
+ this.dataManager.changeColumnOrder(orderBy, orderDirection, sortOrder)
);
+ }
+
isInit && this.dataManager.changeSearchText(props.options.searchText || '');
isInit &&
this.dataManager.changeSearchDebounce(props.options.searchDebounceDelay);
@@ -441,25 +459,36 @@ export default class MaterialTable extends React.Component {
this.setState(this.dataManager.getRenderState());
};
- onChangeOrder = (orderBy, orderDirection) => {
- const newOrderBy = orderDirection === '' ? -1 : orderBy;
- this.dataManager.changeOrder(newOrderBy, orderDirection);
+ onChangeOrder = (orderBy, orderDirection, sortOrder) => {
+ this.dataManager.changeColumnOrder(orderBy, orderDirection, sortOrder);
if (this.isRemoteData()) {
const query = { ...this.state.query };
query.page = 0;
query.orderBy = this.state.columns.find(
- (a) => a.tableData.id === newOrderBy
+ (a) => a.tableData.id === orderBy
);
query.orderDirection = orderDirection;
+ console.warn(
+ 'Properties orderBy and orderDirection had been deprecated when remote data, please start using orderByCollection instead'
+ );
+ query.orderByCollection = this.dataManager.getOrderByCollection();
this.onQueryChange(query, () => {
this.props.onOrderChange &&
- this.props.onOrderChange(newOrderBy, orderDirection);
+ this.props.onOrderChange(orderBy, orderDirection);
+ this.props.onOrderCollectionChange &&
+ this.props.onOrderCollectionChange(
+ this.dataManager.getOrderByCollection()
+ );
});
} else {
this.setState(this.dataManager.getRenderState(), () => {
this.props.onOrderChange &&
- this.props.onOrderChange(newOrderBy, orderDirection);
+ this.props.onOrderChange(orderBy, orderDirection);
+ this.props.onOrderCollectionChange &&
+ this.props.onOrderCollectionChange(
+ this.dataManager.getOrderByCollection()
+ );
});
}
};
@@ -949,6 +978,7 @@ export default class MaterialTable extends React.Component {
);
}
}
+
renderTable = (props) => (
a.position === 'row' || typeof a === 'function'
)
}
- orderBy={this.state.orderBy}
- orderDirection={this.state.orderDirection}
onAllSelected={this.onAllSelected}
onOrderChange={this.onChangeOrder}
+ orderByCollection={this.dataManager.getOrderByCollection()}
isTreeData={this.props.parentChildData !== undefined}
treeDataMaxLevel={this.state.treeDataMaxLevel}
onColumnResized={this.onColumnResized}
scrollWidth={this.state.width}
+ allowSorting={this.dataManager.maxColumnSort !== 0}
/>
)}
this.props.options.exportAll ? this.state.data : this.state.renderData;
@@ -1283,3 +1314,34 @@ function functionlessColumns(columns) {
}, {})
);
}
+
+function getDefaultCollectionSort(currentColumns, prevColumns, maxColumnSort) {
+ let defaultCollectionSort = [];
+ let prevCollectionSort = [];
+
+ if (maxColumnSort > 0) {
+ defaultCollectionSort = reduceByDefaultSort(currentColumns);
+ }
+
+ if (prevColumns) {
+ prevCollectionSort = reduceByDefaultSort(prevColumns);
+ }
+
+ return [
+ defaultCollectionSort.slice(0, maxColumnSort),
+ prevCollectionSort.slice(0, maxColumnSort)
+ ];
+}
+
+function reduceByDefaultSort(list) {
+ return list.reduce((acc, column, index) => {
+ if (column.defaultSort && column.sorting !== false) {
+ acc.push({
+ orderBy: index,
+ orderDirection: column.defaultSort,
+ sortOrder: index
+ });
+ }
+ return acc;
+ }, []);
+}
diff --git a/src/prop-types.js b/src/prop-types.js
index 188e32c5..69f2efe7 100644
--- a/src/prop-types.js
+++ b/src/prop-types.js
@@ -1,4 +1,5 @@
import PropTypes from 'prop-types';
+import { ALL_COLUMNS } from './utils/constants';
const RefComponent = PropTypes.shape({ current: PropTypes.element });
const StyledComponent = PropTypes.shape({
@@ -371,7 +372,30 @@ export const propTypes = {
showSelectGroupCheckbox: PropTypes.bool,
showTitle: PropTypes.bool,
showTextRowsSelected: PropTypes.bool,
- sorting: PropTypes.bool,
+ sorting: PropTypes.bool, // TODO: This will be removed eventually
+ defaultOrderByCollection: PropTypes.arrayOf(
+ PropTypes.shape({
+ orderBy: PropTypes.number,
+ oderDirection: PropTypes.string,
+ orderIndex: PropTypes.number
+ })
+ ),
+ maxColumnSort: PropTypes.oneOf([
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ ALL_COLUMNS
+ ]),
+ showColumnSortOrder: PropTypes.bool,
+ sortOrderIndicatorStyle: PropTypes.object,
keepSortDirectionOnColumnSwitch: PropTypes.bool,
toolbar: PropTypes.bool,
thirdSortClick: PropTypes.bool,
@@ -398,6 +422,7 @@ export const propTypes = {
onPageChange: PropTypes.func,
onChangeColumnHidden: PropTypes.func,
onOrderChange: PropTypes.func,
+ onOrderCollectionChange: PropTypes.func,
onRowClick: PropTypes.func,
onRowDoubleClick: PropTypes.func,
onTreeExpandChange: PropTypes.func,
diff --git a/src/utils/constants.js b/src/utils/constants.js
new file mode 100644
index 00000000..84660d66
--- /dev/null
+++ b/src/utils/constants.js
@@ -0,0 +1 @@
+export const ALL_COLUMNS = 'all_columns';
diff --git a/src/utils/data-manager.js b/src/utils/data-manager.js
index 1d26eaa3..27cdbdfb 100644
--- a/src/utils/data-manager.js
+++ b/src/utils/data-manager.js
@@ -2,6 +2,7 @@ import formatDate from 'date-fns/format';
import uuid from 'uuid';
import { selectFromObject } from './';
import { widthToNumber } from './common-values';
+import { ALL_COLUMNS } from './constants';
export default class DataManager {
checkForId = false;
@@ -12,8 +13,8 @@ export default class DataManager {
detailPanelType = 'multiple';
lastDetailPanelRow = undefined;
lastEditingRow = undefined;
- orderBy = -1;
- orderDirection = 'desc';
+ maxColumnSort = 1;
+ orderByCollection = [];
pageSize = 5;
paging = true;
parentFunc = null;
@@ -183,6 +184,26 @@ export default class DataManager {
this.defaultExpanded = expanded;
}
+ setMaxColumnSort(maxColumnSort) {
+ const availableColumnsLength = this.columns.filter(
+ (column) => column.sorting !== false
+ ).length;
+
+ if (maxColumnSort === ALL_COLUMNS) {
+ this.maxColumnSort = availableColumnsLength;
+ } else {
+ this.maxColumnSort = Math.min(maxColumnSort, availableColumnsLength);
+ }
+ }
+
+ setOrderByCollection() {
+ this.orderByCollection = this.columns.map((columnDef) => ({
+ orderBy: columnDef.tableData.id,
+ sortOrder: undefined,
+ orderDirection: ''
+ }));
+ }
+
changeApplySearch(applySearch) {
this.applySearch = applySearch;
this.searched = false;
@@ -369,11 +390,55 @@ export default class DataManager {
setCheck([currentGroup]);
};
- changeOrder(orderBy, orderDirection) {
- this.orderBy = orderBy;
- this.orderDirection = orderDirection;
- this.currentPage = 0;
+ getOrderByCollection = () => {
+ return this.orderByCollection.filter((collection) => collection.sortOrder);
+ };
+
+ sortOrderCollection = (list) => {
+ return list.sort((a, b) => {
+ if (!a.sortOrder) return 1;
+ if (!b.sortOrder) return -1;
+ return a.sortOrder - b.sortOrder;
+ });
+ };
+
+ changeColumnOrder(orderBy, orderDirection, sortOrder) {
+ let prevColumns = [];
+ const sortColumns = this.getOrderByCollection();
+
+ if (sortColumns.length === this.maxColumnSort && !sortOrder) {
+ this.orderByCollection[0].orderDirection = '';
+ this.orderByCollection[0].sortOrder = undefined;
+
+ prevColumns = this.orderByCollection.map((collection) => {
+ if (collection.sortOrder) {
+ collection.sortOrder -= 1;
+ } else if (collection.orderBy === orderBy && orderDirection) {
+ collection.sortOrder = sortColumns.length;
+ collection.orderDirection = orderDirection;
+ }
+
+ return collection;
+ });
+ } else {
+ prevColumns = this.orderByCollection.map((collection) => {
+ if (collection.orderBy === orderBy && orderDirection) {
+ collection.orderDirection = orderDirection;
+ collection.sortOrder = sortOrder || sortColumns.length + 1;
+ } else if (!orderDirection && collection.orderBy === orderBy) {
+ collection.orderDirection = orderDirection;
+ collection.sortOrder = undefined;
+ } else if (!orderDirection && sortOrder < collection.sortOrder) {
+ collection.sortOrder -= 1;
+ }
+ return collection;
+ });
+ }
+
+ prevColumns = this.sortOrderCollection(prevColumns);
+ this.orderByCollection = [...prevColumns];
+ this.currentPage = 0;
this.sorted = false;
}
@@ -708,39 +773,59 @@ export default class DataManager {
}
sortList(list) {
- let columnDef = this.columns.find((_) => _.tableData.id === this.orderBy);
- if (!columnDef) {
- columnDef = this.columns[0];
- }
- let result = list;
+ const collectionIds = this.orderByCollection.map(
+ (collection) => collection.orderBy
+ );
+ const columnsDefs = new Map();
+ this.columns.forEach((column) => {
+ const columnId = column.tableData.id;
+ if (collectionIds.includes(columnId)) {
+ columnsDefs.set(columnId, column);
+ }
+ });
+
+ const sort = this.sort;
+ const getFieldValue = this.getFieldValue;
+ const orderByCollection = this.orderByCollection;
+
+ return list.sort(function sortData(
+ a,
+ b,
+ columns = columnsDefs,
+ collection = orderByCollection
+ ) {
+ const { orderBy, orderDirection } = collection[0];
+
+ const columnDef = columns.get(orderBy);
- if (columnDef.customSort) {
- if (this.orderDirection === 'desc') {
- result = list.sort((a, b) => columnDef.customSort(b, a, 'row', 'desc'));
+ let compareValue = 0;
+ if (columnDef.customSort) {
+ if (orderDirection === 'desc') {
+ compareValue = columnDef.customSort(b, a, 'row', orderDirection);
+ } else {
+ compareValue = columnDef.customSort(a, b, 'row', orderDirection);
+ }
} else {
- result = list.sort((a, b) =>
- columnDef.customSort(a, b, 'row', this.orderDirection)
+ // Calculate compare value and modify based on order
+ compareValue = sort(
+ getFieldValue(a, columnDef),
+ getFieldValue(b, columnDef),
+ columnDef.type
);
+
+ compareValue =
+ orderDirection.toLowerCase() === 'desc'
+ ? compareValue * -1
+ : compareValue;
}
- } else {
- result = list.sort(
- this.orderDirection === 'desc'
- ? (a, b) =>
- this.sort(
- this.getFieldValue(b, columnDef),
- this.getFieldValue(a, columnDef),
- columnDef.type
- )
- : (a, b) =>
- this.sort(
- this.getFieldValue(a, columnDef),
- this.getFieldValue(b, columnDef),
- columnDef.type
- )
- );
- }
- return result;
+ // See if the next key needs to be considered
+ const checkNextKey = compareValue === 0 && collection.length !== 1;
+
+ return checkNextKey
+ ? sortData(a, b, columns, collection.slice(1))
+ : compareValue;
+ });
}
getRenderState = () => {
@@ -773,8 +858,8 @@ export default class DataManager {
currentPage: this.currentPage,
data: this.sortedData,
lastEditingRow: this.lastEditingRow,
- orderBy: this.orderBy,
- orderDirection: this.orderDirection,
+ orderByCollection: this.orderByCollection,
+ maxColumnSort: this.maxColumnSort,
originalData: [...this.data],
pageSize: this.pageSize,
renderData: this.pagedData,
@@ -1188,9 +1273,12 @@ export default class DataManager {
element.groupsIndex = getGroupsIndex(element.groups);
sortGroupData(element.groups, level + 1);
} else {
- if (this.orderBy >= 0 && this.orderDirection) {
+ if (
+ this.maxColumnSort > 0 &&
+ this.getOrderByCollection().length > 0
+ ) {
element.data = this.sortList(element.data);
- } else if (this.orderDirection === '') {
+ } else if (this.maxColumnSort > 0) {
element.data = element.data.sort((a, b) => {
return (
this.data.findIndex(
@@ -1209,7 +1297,7 @@ export default class DataManager {
sortGroupData(this.sortedData, 1);
} else if (this.isDataType('tree')) {
this.sortedData = [...this.treefiedData];
- if (this.orderBy != -1) {
+ if (this.maxColumnSort > 0 && this.getOrderByCollection().length > 0) {
this.sortedData = this.sortList(this.sortedData);
const sortTree = (list) => {
@@ -1227,7 +1315,11 @@ export default class DataManager {
}
} else if (this.isDataType('normal')) {
this.sortedData = [...this.searchedData];
- if (this.orderBy != -1 && this.applySort) {
+ if (
+ this.maxColumnSort > 0 &&
+ this.getOrderByCollection().length > 0 &&
+ this.applySort
+ ) {
this.sortedData = this.sortList(this.sortedData);
}
}