Compare commits

..
7 Commits
Author SHA1 Message Date
Tanner Linsley 9acf8f37eb 5.4.0 2017-03-30 10:21:59 -06:00
Mike DeVitaandTanner Linsley 64a427d91b fix broken TOC Link (#157)
Custom Cell Header Rendering link was broken in table of contents.
2017-03-30 09:53:52 -06:00
Mike DeVitaandTanner Linsley d21ce226aa create ISSUE_TEMPLATE.md (#158)
hopefully this will alleviate some clutter on issues.
2017-03-30 09:52:40 -06:00
Tanner LinsleyandGitHub 92c9ae2196 minRows now works for datasets larger than 1 page
Closes #154
2017-03-29 18:02:33 -06:00
Aaron SchwartzandTanner Linsley 23a031b48a Add column filtering (#147)
* Add column filtering.

* Fix javascript warning from yarn test. Compile storybook and docs.

* Pass standard linting

* Add support for filtering pivot columns.

* Build distribution files.
2017-03-29 16:46:04 -06:00
Jolyon RussandTanner Linsley bd8b273dea Added an id in the example so it doesn't error (#151) 2017-03-29 07:08:54 -06:00
Tanner Linsley 1a9f0e57b4 5.3.5 2017-03-27 11:57:44 -06:00
13 changed files with 151 additions and 73 deletions
+15
View File
@@ -0,0 +1,15 @@
## Problem Description
include a detailed explanation of the problem here...
## Code Snippet(s)
## Steps to Reproduce
1. list the steps
2. to reproduce the issue
## System Information
**Browser & Browser Version:**
**Node Version:**
**OS Version:**
**NPM Version:**
**Yarn Version:**
+30 -12
View File
@@ -47,7 +47,7 @@
- [Props](#props)
- [Columns](#columns)
- [Column Header Groups](#column-header-groups)
- [Custom Cell and Header Rendering](#custom-cell-and-header-rendering)
- [Custom Cell and Header and Footer Rendering](#custom-cell-header-and-footer-rendering)
- [Styles](#styles)
- [Custom Props](#custom-props)
- [Pivoting and Aggregation](#pivoting-and-aggregation)
@@ -56,6 +56,7 @@
- [Fully Controlled Component](#fully-controlled-component)
- [Functional Rendering](#functional-rendering)
- [Multi-Sort](#multi-sort)
- [Filtering](#filtering)
- [Component Overrides](#component-overrides)
- [Contributing](#contributing)
- [Scripts](#scripts)
@@ -108,6 +109,7 @@ const columns = [{
accessor: 'age',
render: props => <span className='number'>{props.value}</span> // Custom cell components!
}, {
id: 'friendName', // Required because our accessor is not a string
header: 'Friend Name',
accessor: d => d.friend.name // Custom value accessors!
}, {
@@ -145,7 +147,10 @@ These are all of the available props (and their default values) for the main `<R
defaultSorting: [],
showFilters: false,
defaultFiltering: [],
defaultFilterMethod: (filter, row) => (row[filter.id] == filter.value),
defaultFilterMethod: (filter, row, column) => {
const id = filter.pivotId || filter.id
return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true
},
// Controlled State Overrides (see Fully Controlled Component section)
page: undefined,
@@ -223,7 +228,8 @@ These are all of the available props (and their default values) for the main `<R
footerClassName: '',
footerStyle: {},
getFooterProps: () => ({}),
filterMethod: undefined
filterMethod: undefined,
hideFilter: false
},
// Text
@@ -265,16 +271,16 @@ Or just define them as props
```javascript
[{
// General
accessor: 'propertyName' // or Accessor eg. (row) => row.propertyName (see "Accessors" section for more details)
accessor: 'propertyName', // or Accessor eg. (row) => row.propertyName (see "Accessors" section for more details)
id: 'myProperty', // Conditional - A unique ID is required if the accessor is not a string or if you would like to override the column name used in server-side calls
sortable: true,
show: true, // can be used to hide a column
width: undefined, // A hardcoded width for the column. This overrides both min and max width options
minWidth: 100 // A minimum width for this column. If there is extra room, column will flex to fill available space (up to the max-width, if set)
maxWidth: undefined // A maximum width for this column.
minWidth: 100, // A minimum width for this column. If there is extra room, column will flex to fill available space (up to the max-width, if set)
maxWidth: undefined, // A maximum width for this column.
// Special
expander: false // This option will override all data-related options and designates the column to be used
expander: false, // This option will override all data-related options and designates the column to be used
// for pivoting and sub-component expansion
// Cell Options
@@ -291,22 +297,23 @@ Or just define them as props
header: 'Header Name', a function that returns a primitive, or JSX / React Component eg. ({data, column}) => <div>Header Name</div>,
headerClassName: '', // Set the classname of the `th` element of the column
headerStyle: {}, // Set the style of the `th` element of the column
getHeaderProps: (state, rowInfo, column, instance) => ({}) // a function that returns props to decorate the `th` element of the column
getHeaderProps: (state, rowInfo, column, instance) => ({}), // a function that returns props to decorate the `th` element of the column
// Header Groups only
columns: [...] // See Header Groups section below
columns: [...], // See Header Groups section below
// Footer
footer: 'Header Name' or JSX eg. ({data, column}) => <div>Header Name</div>,
footerClassName: '', // Set the classname of the `td` element of the column's footer
footerStyle: {}, // Set the style of the `td` element of the column's footer
getFooterProps: (state, rowInfo, column, instance) => ({}) // A function that returns props to decorate the `td` element of the column's footer
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]}
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
hideFilter: false // If `showFilters` is set on the table, this option will let you selectively hide the filter on a particular row
}]
```
@@ -659,6 +666,17 @@ The possibilities are endless!
## Multi-Sort
When clicking on a column header, hold shift to multi-sort! You can toggle `ascending` `descending` and `none` for multi-sort columns. Clicking on a header without holding shift will clear the multi-sort and replace it with the single sort of that column. It's quite handy!
## Filtering
Filtering can be enabled by setting the `showFilters` option on the table.
If you don't want particular column to be filtered you can set the `hideFilter` option on the column.
By default the table tries to filter by checking if the row's value starts with the filter text. The default method for filtering the table can be set with the table's `defaultFilterMethod` option.
If you want to override a particular column's filtering method, you can set the `filterMethod` option on a column.
See <a href="http://react-table.js.org/?selectedKind=2.%20Demos&selectedStory=Custom%20Filtering&full=0&down=1&left=1&panelRight=0&downPanel=kadirahq%2Fstorybook-addon-actions%2Factions-panel" target="\_parent">Custom Filtering</a> demo for examples.
## Component Overrides
Though we confidently stand by the markup and architecture behind it, `react-table` does offer the ability to change the core componentry it uses to render everything. You can extend or override these internal components by passing a react component to it's corresponding prop on either the global props or on a one-off basis like so:
```javascript
+1 -1
View File
@@ -16,7 +16,7 @@
<body>
<div id="root"></div>
<div id="error-display"></div>
<script src="static/preview.ea1bdf726a82703c8f6f.bundle.js"></script>
<script src="static/preview.31fe462e1d0d70b4c4a4.bundle.js"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"version":3,"file":"static/preview.31fe462e1d0d70b4c4a4.bundle.js","sources":["webpack:///static/preview.31fe462e1d0d70b4c4a4.bundle.js"],"mappings":"AAAA;AAkuDA;AAw+DA;AAurFA;AAgmFA;AA87OA;AAuwFA;AA6xDA;AAy/DA;AA+uDA;AAi8DA;AA+lDA;AA4yDA;AA28CA;AAw7DA;AAooDA;AA67CA;AAiqDA;AAynEA;AAwxDA;AA+rCA;AA+mDA;AA2lDA;AAkqEA;AAs2DA;AA2zDA;AAo4CA;AAosFA;AAmjGA;AA2sDA;AA82CA;AAooCA;AAg9CA;AA67CA;AAoDA;AA+jFA","sourceRoot":""}
@@ -1 +0,0 @@
{"version":3,"file":"static/preview.ea1bdf726a82703c8f6f.bundle.js","sources":["webpack:///static/preview.ea1bdf726a82703c8f6f.bundle.js"],"mappings":"AAAA;AAkuDA;AAo/DA;AAgrFA;AAmmFA;AA07OA;AAuwFA;AAwtDA;AA+jEA;AA4uDA;AAi8DA;AA+lDA;AA4yDA;AA28CA;AAw7DA;AAooDA;AA67CA;AAiqDA;AAynEA;AAwxDA;AA+rCA;AA+mDA;AA2lDA;AAkqEA;AAs2DA;AA2zDA;AAo4CA;AAksFA;AAmjGA;AAksDA;AA02CA;AAgoCA;AAm/CA;AA+4CA;AAsCA;AA+jFA","sourceRoot":""}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "react-table",
"version": "5.3.4",
"version": "5.4.0",
"description": "A fast, lightweight, opinionated table and datagrid built on React",
"license": "MIT",
"homepage": "https://github.com/tannerlinsley/react-table#readme",
@@ -61,7 +61,7 @@
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-json-tree": "^0.10.1",
"rimraf": "^2.6.1",
"rimraf": "^2.6.1",
"standard": "^8.0.0",
"storybook": "^0.0.0",
"stylus": "^0.54.5",
+6 -2
View File
@@ -23,7 +23,10 @@ export default {
defaultSorting: [],
showFilters: false,
defaultFiltering: [],
defaultFilterMethod: (filter, row, column) => (row[filter.id] === filter.value),
defaultFilterMethod: (filter, row, column) => {
const id = filter.pivotId || filter.id
return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true
},
// Controlled State Overrides
// page: undefined,
@@ -100,7 +103,8 @@ export default {
footerClassName: '',
footerStyle: {},
getFooterProps: emptyObj,
filterMethod: undefined
filterMethod: undefined,
hideFilter: false
},
// Text
+21 -21
View File
@@ -85,9 +85,7 @@ export default React.createClass({
const endRow = startRow + pageSize
const pageRows = manual ? resolvedData : sortedData.slice(startRow, endRow)
const minRows = this.getMinRows()
const padRows = pages > 1 ? _.range(pageSize - pageRows.length)
: minRows ? _.range(Math.max(minRows - pageRows.length, 0))
: []
const padRows = _.range(Math.max(minRows - pageRows.length, 0))
const hasColumnFooter = allVisibleColumns.some(d => d.footer)
@@ -416,20 +414,20 @@ export default React.createClass({
const pivotCols = []
for (let i = 0; i < column.pivotColumns.length; i++) {
const col = column.pivotColumns[i]
const filter = filtering.find(filter => filter.id === col.id)
const filter = filtering.find(filter => filter.id === column.id && filter.pivotId === col.id)
pivotCols.push(
<span key={col.id}
style={{display: 'flex', alignContent: 'flex-end', flex: 1}}>
<input type='text'
style={{
flex: 1,
width: 20,
backgroundColor: i > 0 ? '#f3f3f3' : '#fff'
}}
value={filter ? filter.value : ''}
disabled={i > 0}
onChange={(event) => this.filterColumn(col, event)}
/>
{!col.hideFilter ? (
<input type='text'
style={{
flex: 1,
width: 20
}}
value={filter ? filter.value : ''}
onChange={(event) => this.filterColumn(column, event, col)}
/>
) : null}
</span>
)
if (i < column.pivotColumns.length - 1) {
@@ -490,13 +488,15 @@ export default React.createClass({
}}
{...rest}
>
<input type='text'
style={{
width: `100%`
}}
value={filter ? filter.value : ''}
onChange={(event) => this.filterColumn(column, event)}
/>
{!column.hideFilter ? (
<input type='text'
style={{
width: `100%`
}}
value={filter ? filter.value : ''}
onChange={(event) => this.filterColumn(column, event)}
/>
) : null}
</ThComponent>
)
}
+48 -9
View File
@@ -241,10 +241,9 @@ export default {
// Resolve the data from either manual data or sorted data
return {
sortedData: manual ? resolvedData : this.sortData(resolvedData, sorting, showFilters, filtering, defaultFilterMethod, allVisibleColumns)
sortedData: manual ? resolvedData : this.sortData(this.filterData(resolvedData, showFilters, filtering, defaultFilterMethod, allVisibleColumns), sorting)
}
},
fireOnChange () {
this.props.onChange(this.getResolvedState(), this)
},
@@ -254,7 +253,7 @@ export default {
getStateOrProp (key) {
return _.getFirstDefined(this.state[key], this.props[key])
},
sortData (data, sorting, showFilters, filtering, defaultFilterMethod, allVisibleColumns) {
filterData (data, showFilters, filtering, defaultFilterMethod, allVisibleColumns) {
let filteredData = data
if (showFilters && filtering.length) {
@@ -262,20 +261,49 @@ export default {
(filteredSoFar, nextFilter) => {
return filteredSoFar.filter(
(row) => {
const column = allVisibleColumns.find(x => x.id === nextFilter.id) || {}
let column
if (nextFilter.pivotId) {
const parentColumn = allVisibleColumns.find(x => x.id === nextFilter.id)
column = parentColumn.pivotColumns.find(x => x.id === nextFilter.pivotId)
} else {
column = allVisibleColumns.find(x => x.id === nextFilter.id)
}
const filterMethod = column.filterMethod || defaultFilterMethod
return filterMethod(nextFilter, row, column)
})
}
, filteredData
)
// Apply the filter to the subrows if we are pivoting, and then
// filter any rows without subcolumns because it would be strange to show
filteredData = filteredData.map(row => {
if (!row[this.props.subRowsKey]) {
return row
}
return {
...row,
[this.props.subRowsKey]: this.filterData(row[this.props.subRowsKey], showFilters, filtering, defaultFilterMethod, allVisibleColumns)
}
}).filter(row => {
if (!row[this.props.subRowsKey]) {
return true
}
return row[this.props.subRowsKey].length > 0
})
}
return filteredData
},
sortData (data, sorting) {
if (!sorting.length) {
return filteredData
return data
}
const sorted = _.orderBy(filteredData, sorting.map(sort => {
const sorted = _.orderBy(data, sorting.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
return -Infinity
@@ -419,7 +447,7 @@ export default {
this.fireOnChange()
})
},
filterColumn (column, event) {
filterColumn (column, event, pivotColumn) {
const {filtering} = this.getResolvedState()
const {onFilteringChange} = this.props
@@ -428,12 +456,23 @@ export default {
}
// Remove old filter first if it exists
const newFiltering = (filtering || []).filter(x => x.id !== column.id)
const newFiltering = (filtering || []).filter(x => {
if (x.id !== column.id) {
return true
}
if (x.pivotId) {
if (pivotColumn) {
return x.pivotId !== pivotColumn.id
}
return true
}
})
if (event.target.value !== '') {
newFiltering.push({
id: column.id,
value: event.target.value
value: event.target.value,
pivotId: pivotColumn ? pivotColumn.id : undefined
})
}
+4 -4
View File
@@ -89,7 +89,7 @@ class Filtering extends React.Component {
data={this.state.data}
columns={columns}
defaultPageSize={10}
defaultFilterMethod={(filter, row) => ((row[filter.id] + "").startsWith(filter.value))}
defaultFilterMethod={(filter, row) => (String(row[filter.id]) === filter.value)}
{...this.state.tableOptions}
SubComponent={(row) => {
return (
@@ -122,8 +122,8 @@ class Filtering extends React.Component {
</div>
<div>
<h1>Custom Filters In This Example</h1>
<p>The default filter for all columns of a table if it is not specified in the configuration is set to be an exact match. Example: age == "23".</p>
<p>This example overrides the default filter behavior by setting the <strong>defaultFilterMethod</strong> table option to match on values that start with the filter text. Example: age.startsWith("2")</p>
<p>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").</p>
<p>This example overrides the default filter behavior by setting the <strong>defaultFilterMethod</strong> table option to match on values that are exactly equal to the filter text. Example: age == "23")</p>
<p>Each column can also be customized with the column <strong>filterMethod</strong> option:</p>
<p>In this example the firstName column filters on the value starting with and ending with the filter value.</p>
<p>In this example the lastName column filters on the value including the filter value anywhere in its text.</p>
@@ -172,7 +172,7 @@ export default (
data={data}
columns={columns}
defaultPageSize={10}
defaultFilterMethod={(filter, row) => ((row[filter.id] + \"\").startsWith(filter.value))}
defaultFilterMethod={(filter, row) => (String(row[filter.id]) === filter.value)}
{...otherOptions}
SubComponent={(row) => {
return (
+4 -2
View File
@@ -38,7 +38,8 @@ export default () => {
}, {
header: 'Visits',
accessor: 'visits',
aggregate: vals => _.sum(vals)
aggregate: vals => _.sum(vals),
hideFilter: true
}]
}]
@@ -111,7 +112,8 @@ const columns = [{
}, {
header: 'Visits',
accessor: 'visits',
aggregate: vals => _.sum(vals)
aggregate: vals => _.sum(vals),
hideFilter: true
}]
}]
+3 -3
View File
@@ -18,13 +18,13 @@ const requestData = (pageSize, page, sorting, filtering) => {
return new Promise((resolve, reject) => {
// On the server, you'll likely use SQL or noSQL or some other query language to do this.
// For this mock, we'll just use lodash
let filteredData = rawData
let filteredData = rawData;
if (filtering.length) {
filteredData = filtering.reduce(
(filteredSoFar, nextFilter) => {
return filteredSoFar.filter(
(row) => {
return (row[nextFilter.id] + '').includes(nextFilter.value)
return (row[nextFilter.id]+"").includes(nextFilter.value)
})
}
, filteredData)
@@ -93,7 +93,7 @@ const ServerSide = React.createClass({
}]}
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
defaultPageSize={10}
showFilters
showFilters={true}
data={this.state.data} // Set the rows to be displayed
pages={this.state.pages} // Display the total number of pages
loading={this.state.loading} // Display the loading overlay when we need it