From 5ac2c1779d0a02580b71d6760f736f91605763b8 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Wed, 5 Jul 2017 10:31:50 -0600 Subject: [PATCH] Feature: column.filterAll + docs --- CHANGELOG.md | 4 + README.md | 14 ++- docs/src/stories/Filtering.js | 224 ++++++++++++++++++++-------------- package.json | 22 +++- src/defaultProps.js | 1 + src/methods.js | 25 ++-- yarn.lock | 81 ++++-------- 7 files changed, 208 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 148743f..39d3fa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.5.0 +##### New Features +- `column.filterAll` - defaults to `false`, but when set to `true` will provide the entire array of rows to `filterMethod` as opposed to one row at a time. This allows for more fine-grained filtering using any method you can dream up. See the [Custom Filtering example](https://react-table.js.org/#/story/custom-filtering) for more info. + ## 6.4.0 ##### New Features - `PadRowComponent` - the content rendered inside of a padding row. Defaults to a react component that renders ` ` diff --git a/README.md b/README.md index 0d50eaf..efbec40 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,7 @@ These are all of the available props (and their default values) for the main ` ({}), + filterAll: false, filterMethod: undefined, sortMethod: undefined, defaultSortDesc: undefined, @@ -397,10 +398,11 @@ Or just define them as props getFooterProps: (state, rowInfo, column, instance) => ({}), // A function that returns props to decorate the `td` element of the column's footer // Filtering - filterMethod: (filter, row, column) => {return true}, // A function returning a boolean that specifies the filtering logic for the column - // filter == an object specifying which filter is being applied. Format: {id: [the filter column's id], value: [the value the user typed in the filter field], pivotId: [if filtering on a pivot column, the pivotId will be set to the pivot column's id and the `id` field will be set to the top level pivoting column]} - // row == the row of data supplied to the table - // column == the column that the filter is on + filterMethod: (filter, row || rows, column) => {return true}, // A function returning a boolean that specifies the filtering logic for the column + // 'filter' == an object specifying which filter is being applied. Format: {id: [the filter column's id], value: [the value the user typed in the filter field], pivotId: [if filtering on a pivot column, the pivotId will be set to the pivot column's id and the `id` field will be set to the top level pivoting column]} + // 'row' || 'rows' == the row (or rows, if filterAll is set to true) of data supplied to the table + // 'column' == the column that the filter is on + filterAll: false }] ``` @@ -854,6 +856,10 @@ By default the table tries to filter by checking if the row's value starts with If you want to override a particular column's filtering method, you can set the `filterMethod` option on a column. +By default, `filterMethod` is passed a single row of data at a time, and you are responsible for returning `true` or `false`, indicating whether it should be shown. + +Alternatively, you can set `filterAll` to `true`, and `fitlerMethod` will be passed the entire array of rows to be filtered, and you will then be responsible for returning the new filtered array. This is extremely handy when you need to utilize a utility like fuzzy matching that requires the entire array of items. + To completely override the filter that is shown, you can set the `Filter` column option. Using this option you can specify the JSX that is shown. The option is passed an `onChange` method which must be called with the the value that you wan't to pass to the `filterMethod` option whenever the filter has changed. See Custom Filtering demo for examples. diff --git a/docs/src/stories/Filtering.js b/docs/src/stories/Filtering.js index 2e2bf4d..b9dc9c6 100644 --- a/docs/src/stories/Filtering.js +++ b/docs/src/stories/Filtering.js @@ -2,19 +2,20 @@ import React from 'react' import _ from 'lodash' import namor from 'namor' +import matchSorter from 'match-sorter' import CodeHighlight from './components/codeHighlight' import ReactTable from '../../../lib/index' class Story extends React.PureComponent { - constructor (props) { + constructor(props) { super(props) const data = _.map(_.range(5553), d => { return { - firstName: namor.generate({words: 1, numbers: 0}), - lastName: namor.generate({words: 1, numbers: 0}), + firstName: namor.generate({ words: 1, numbers: 0 }), + lastName: namor.generate({ words: 1, numbers: 0 }), age: Math.floor(Math.random() * 30) } }) @@ -39,52 +40,63 @@ class Story extends React.PureComponent { this.setTableOption = this.setTableOption.bind(this) } - render () { - const columns = [{ - Header: 'Name', - columns: [{ - Header: 'First Name', - accessor: 'firstName', - filterMethod: (filter, row) => (row[filter.id].startsWith(filter.value) && row[filter.id].endsWith(filter.value)) - }, { - Header: 'Last Name', - id: 'lastName', - accessor: d => d.lastName, - filterMethod: (filter, row) => (row[filter.id].includes(filter.value)) - }] - }, { - Header: 'Info', - columns: [{ - Header: 'Age', - accessor: 'age' - }, { - Header: 'Over 21', - accessor: 'age', - id: 'over', - Cell: ({value}) => (value >= 21 ? 'Yes' : 'No'), - filterMethod: (filter, row) => { - if (filter.value === 'all') { - return true + render() { + const columns = [ + { + Header: 'Name', + columns: [ + { + Header: 'First Name', + accessor: 'firstName', + filterMethod: (filter, row) => + row[filter.id].startsWith(filter.value) && + row[filter.id].endsWith(filter.value) + }, + { + Header: 'Last Name', + id: 'lastName', + accessor: d => d.lastName, + filterMethod: (filter, rows) => + matchSorter(rows, filter.value, { keys: ['lastName'] }), + filterAll: true } - if (filter.value === 'true') { - return row[filter.id] >= 21 + ] + }, + { + Header: 'Info', + columns: [ + { + Header: 'Age', + accessor: 'age' + }, + { + Header: 'Over 21', + accessor: 'age', + id: 'over', + Cell: ({ value }) => (value >= 21 ? 'Yes' : 'No'), + filterMethod: (filter, row) => { + if (filter.value === 'all') { + return true + } + if (filter.value === 'true') { + return row[filter.id] >= 21 + } + return row[filter.id] < 21 + }, + Filter: ({ filter, onChange }) => + } - return row[filter.id] < 21 - }, - Filter: ({filter, onChange}) => ( - - ) + ] } - ] - }] + ] return (
@@ -92,38 +104,43 @@ class Story extends React.PureComponent {

Table Options

- { - Object.keys(this.state.tableOptions).map(optionKey => { - const optionValue = this.state.tableOptions[optionKey] - return ( - - - - - ) - }) - } + {Object.keys(this.state.tableOptions).map(optionKey => { + const optionValue = this.state.tableOptions[optionKey] + return ( + + + + + ) + })}
{optionKey} - -
+ {optionKey} + + +
-
+
(String(row[filter.id]) === filter.value)} + defaultFilterMethod={(filter, row) => + String(row[filter.id]) === filter.value} {...this.state.tableOptions} - SubComponent={(row) => { + SubComponent={row => { return ( -
- You can put any component you want here, even another React Table! +
+ + You can put any component you want here, even another React + Table! +

{ + SubComponent={row => { return ( -
+
It even has access to the row data: - {() => JSON.stringify(row, null, 2)} + + {() => JSON.stringify(row, null, 2)} +
) }} @@ -145,32 +164,54 @@ class Story extends React.PureComponent { }} />
-
+

Tip: Hold shift when sorting to multi-sort!

Custom Filters In This Example

-

The default filter for all columns of a table if it is not specified in the configuration is set to match - on values that start with the filter text. Example: age.startsWith("2").

-

This example overrides the default filter behavior by setting - the defaultFilterMethod table option to match on values that are exactly equal to the - filter text. Example: age == "23")

-

Each column can also be customized with the column filterMethod option:

-

In this example the firstName column filters on the value starting with and ending with the filter - value.

-

In this example the lastName column filters on the value including the filter value anywhere in its - text.

-

To completely override the filter that is shown, you can set the Filter column - option. Using this option you can specify the JSX that is shown. The option is passed - an onChange method that must be called with the value that you wan't to - pass to the filterMethod option whenever the filter has changed.

+

+ The default filter for all columns of a table if it is not specified + in the configuration is set to match on values that start with the + filter text. Example: age.startsWith("2"). +

+

+ This example overrides the default filter behavior by setting the{' '} + defaultFilterMethod table option to match on values + that are exactly equal to the filter text. Example: age == "23") +

+

+ Each column can also be customized with the column{' '} + filterMethod option: +

+

+ In this example the firstName column filters on the value starting + with and ending with the filter value. +

+

+ In this example the lastName column filters using{' '} + Kent C. Dodd's{' '} + + match-sorter + {' '} + fuzzy search module. Note that 'filterAll' is set to true, which + allows us to filter using the entire dataset instead of just one row + at a time. +

+

+ To completely override the filter that is shown, you can set the{' '} + Filter column option. Using this option you can + specify the JSX that is shown. The option is passed an{' '} + onChange method that must be called with the value + that you wan't to pass to the filterMethod option + whenever the filter has changed. +

) } - setTableOption (event) { + setTableOption(event) { const target = event.target const value = target.type === 'checkbox' ? target.checked : target.value const name = target.name @@ -186,9 +227,10 @@ class Story extends React.PureComponent { // Source Code const source = require('!raw!./Filtering') -export default () => ( +export default () =>
- {() => source} + + {() => source} +
-) diff --git a/package.json b/package.json index 2378926..e7a92a6 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,20 @@ "type": "git", "url": "git+https://github.com/tannerlinsley/react-table.git" }, - "keywords": ["react", "table", "react-table", "datagrid"], + "keywords": [ + "react", + "table", + "react-table", + "datagrid" + ], "main": "lib/index.js", - "files": ["src/", "lib/", "react-table.js", "react-table.css", "media/*.png"], + "files": [ + "src/", + "lib/", + "react-table.js", + "react-table.css", + "media/*.png" + ], "scripts": { "build:node": "babel src --out-dir lib --source-maps inline", "build:css": "rimraf react-table.css && stylus src/index.styl --compress -o react-table.css && yarn run css:autoprefix", @@ -40,6 +51,7 @@ "babel-preset-react": "6.11.1", "babel-preset-stage-2": "6.13.0", "eslint": "^4.1.1", + "match-sorter": "^1.8.0", "npm-run-all": "^3.1.1", "onchange": "^3.0.2", "postcss-cli": "^2.6.0", @@ -52,6 +64,10 @@ "webpack": "^2.5.1" }, "babel": { - "presets": ["es2015", "stage-2", "react"] + "presets": [ + "es2015", + "stage-2", + "react" + ] } } diff --git a/src/defaultProps.js b/src/defaultProps.js index 1132149..0be55ef 100644 --- a/src/defaultProps.js +++ b/src/defaultProps.js @@ -145,6 +145,7 @@ export default { footerStyle: {}, getFooterProps: emptyObj, filterMethod: undefined, + filterAll: false, sortMethod: undefined, }, diff --git a/src/methods.js b/src/methods.js index 6116a24..98d3aae 100644 --- a/src/methods.js +++ b/src/methods.js @@ -338,20 +338,23 @@ export default Base => if (filtered.length) { filteredData = filtered.reduce((filteredSoFar, nextFilter) => { - return filteredSoFar.filter(row => { - let column + const column = allVisibleColumns.find(x => x.id === nextFilter.id) - column = allVisibleColumns.find(x => x.id === nextFilter.id) + // Don't filter hidden columns or columns that have had their filters disabled + if (!column || column.filterable === false) { + return filteredSoFar + } - // Don't filter hidden columns or columns that have had their filters disabled - if (!column || column.filterable === false) { - return true - } + const filterMethod = column.filterMethod || defaultFilterMethod - const filterMethod = column.filterMethod || defaultFilterMethod - - return filterMethod(nextFilter, row, column) - }) + // If 'filterAll' is set to true, pass the entire dataset to the filter method + if (column.filterAll) { + return filterMethod(nextFilter, filteredSoFar, column) + } else { + return filteredSoFar.filter(row => { + return filterMethod(nextFilter, row, column) + }) + } }, filteredData) // Apply the filter to the subrows if we are pivoting, and then diff --git a/yarn.lock b/yarn.lock index e4e3071..a14d1bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1293,11 +1293,11 @@ debug-log@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" -debug@*, debug@^2.1.1, debug@^2.2.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" +debug@*, debug@^2.1.1, debug@^2.2.0, debug@^2.6.8: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: - ms "0.7.2" + ms "2.0.0" debug@2.2.0, debug@~2.2.0: version "2.2.0" @@ -1305,12 +1305,6 @@ debug@2.2.0, debug@~2.2.0: dependencies: ms "0.7.1" -debug@^2.6.8: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" - dependencies: - ms "2.0.0" - decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1374,6 +1368,10 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +diacritic@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/diacritic/-/diacritic-0.0.2.tgz#fc2a887b5a5bc0a0a854fb614c7c2f209061ee04" + diffie-hellman@^5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" @@ -1694,10 +1692,6 @@ espree@^3.4.0, espree@^3.4.3: acorn "^5.0.1" acorn-jsx "^3.0.0" -esprima@^2.6.0: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" - esprima@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -2016,7 +2010,7 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@7.0.x, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5: +glob@7.0.x: version "7.0.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" dependencies: @@ -2047,7 +2041,7 @@ glob@^6.0.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.2: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -2058,11 +2052,11 @@ glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^9.0.0, globals@^9.14.0: - version "9.14.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034" +global-object@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-object/-/global-object-1.0.0.tgz#2a1b45e901d55e4773154f12f0cec1ef9aba5f9f" -globals@^9.17.0: +globals@^9.0.0, globals@^9.14.0, globals@^9.17.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -2167,23 +2161,15 @@ https-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" -iconv-lite@^0.4.17: +iconv-lite@^0.4.17, iconv-lite@~0.4.13: version "0.4.18" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" -iconv-lite@~0.4.13: - version "0.4.15" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" - ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" -ignore@^3.0.11, ignore@^3.0.9, ignore@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435" - -ignore@^3.3.3: +ignore@^3.0.11, ignore@^3.0.9, ignore@^3.2.0, ignore@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d" @@ -2339,16 +2325,7 @@ is-glob@^2.0.0, is-glob@^2.0.1: dependencies: is-extglob "^1.0.0" -is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: - version "2.15.0" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b" - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - -is-my-json-valid@^2.16.0: +is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4, is-my-json-valid@^2.16.0: version "2.16.0" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" dependencies: @@ -2460,14 +2437,7 @@ js-tokens@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.0.tgz#a2f2a969caae142fb3cd56228358c89366957bd1" -js-yaml@^3.5.1: - version "3.7.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" - dependencies: - argparse "^1.0.7" - esprima "^2.6.0" - -js-yaml@^3.8.4: +js-yaml@^3.5.1, js-yaml@^3.8.4: version "3.8.4" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.4.tgz#520b4564f86573ba96662af85a8cafa7b4b5a6f6" dependencies: @@ -2656,6 +2626,13 @@ map-stream@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" +match-sorter@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-1.8.0.tgz#29a2732fd6ea1ae7d3f02e592a533597fee5a38f" + dependencies: + diacritic "0.0.2" + global-object "1.0.0" + memory-fs@^0.4.0, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -2721,13 +2698,13 @@ minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3: +"minimatch@2 || 3", minimatch@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" dependencies: brace-expansion "^1.0.0" -minimatch@^3.0.4: +minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -2751,10 +2728,6 @@ ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" -ms@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"