Add column filtering.

This commit is contained in:
Aaron Schwartz
2017-03-28 16:01:52 -07:00
parent 4c11248c69
commit 590afb3e61
12 changed files with 520 additions and 75 deletions
+2
View File
@@ -24,6 +24,7 @@ import FunctionalRendering from '../stories/FunctionalRendering.js'
import CustomExpanderPosition from '../stories/CustomExpanderPosition.js'
import NoDataText from '../stories/NoDataText.js'
import Footers from '../stories/Footers.js'
import Filtering from '../stories/Filtering.js'
//
configure(() => {
storiesOf('1. Docs')
@@ -53,4 +54,5 @@ configure(() => {
.add('Custom Expander Position', CustomExpanderPosition)
.add('Custom "No Data" Text', NoDataText)
.add('Footers', Footers)
.add('Custom Filtering', Filtering)
}, module)
+4
View File
@@ -11,3 +11,7 @@ h1 {
margin-bottom: 15px;
margin-top: 5px;
}
p {
margin-bottom: 15px;
}
+19 -4
View File
@@ -143,6 +143,9 @@ These are all of the available props (and their default values) for the main `<R
collapseOnDataChange: true,
freezeWhenExpanded: false,
defaultSorting: [],
showFilters: false,
defaultFiltering: [],
defaultFilterMethod: (filter, row) => (row[filter.id] == filter.value),
// Controlled State Overrides (see Fully Controlled Component section)
page: undefined,
@@ -154,6 +157,7 @@ These are all of the available props (and their default values) for the main `<R
onPageChange: undefined,
onPageSizeChange: undefined,
onSortingChange: undefined,
onFilteringChange: undefined,
// Pivoting
pivotBy: undefined,
@@ -184,6 +188,9 @@ These are all of the available props (and their default values) for the main `<R
getTheadProps: () => ({}),
getTheadTrProps: () => ({}),
getTheadThProps: () => ({}),
getTheadFilterProps: () => ({}),
getTheadFilterTrProps: () => ({}),
getTheadFilterThProps: () => ({}),
getTbodyProps: () => ({}),
getTrGroupProps: () => ({}),
getTrProps: () => ({}),
@@ -215,7 +222,8 @@ These are all of the available props (and their default values) for the main `<R
footer: undefined,
footerClassName: '',
footerStyle: {},
getFooterProps: () => ({})
getFooterProps: () => ({}),
filterMethod: undefined
},
// Text
@@ -292,8 +300,13 @@ Or just define them as props
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]}
// row == the row of data supplied to the table
// column == the column that the filter is on
}]
```
@@ -532,7 +545,7 @@ By adding a `SubComponent` props, you can easily add an expansion level to all r
## Server-side Data
If you want to handle pagination, and sorting on the server, `react-table` makes it easy on you.
If you want to handle pagination, sorting, and filtering on the server, `react-table` makes it easy on you.
1. Feed React Table `data` from somewhere dynamic. eg. `state`, a redux store, etc...
1. Add `manual` as a prop. This informs React Table that you'll be handling sorting and pagination server-side
@@ -555,7 +568,8 @@ If you want to handle pagination, and sorting on the server, `react-table` makes
Axios.post('mysite.com/data', {
page: state.page,
pageSize: state.pageSize,
sorting: state.sorting
sorting: state.sorting,
filtering: state.filtering
})
.then((res) => {
// Update react-table
@@ -601,6 +615,7 @@ Here are the props and their corresponding callbacks that control the state of t
onPageSizeChange={(pageSize, pageIndex) => {...}} // Called when the pageSize is changed by the user. The resolve page is also sent to maintain approximate position in the data
onSortingChange={(column, shiftKey) => {...}} // Called when a sortable column header is clicked with the column itself and if the shiftkey was held. If the column is a pivoted column, `column` will be an array of columns
onExpandRow={(index, event) => {...}} // Called when an expander is clicked. Use this to manage `expandedRows`
onFilteringChange={(column, event) => {...}} // Called when a user enters a value into a filter input field. The event is the onChange event of the input field.
/>
```
+10 -2
View File
@@ -21,6 +21,9 @@ export default {
collapseOnDataChange: true,
freezeWhenExpanded: false,
defaultSorting: [],
showFilters: false,
defaultFiltering: [],
defaultFilterMethod: (filter, row, column) => (row[filter.id] == filter.value),
// Controlled State Overrides
// page: undefined,
@@ -32,6 +35,7 @@ export default {
onPageChange: undefined,
onPageSizeChange: undefined,
onSortingChange: undefined,
onFilteringChange: undefined,
// Pivoting
pivotBy: undefined,
@@ -62,6 +66,9 @@ export default {
getTheadProps: emptyObj,
getTheadTrProps: emptyObj,
getTheadThProps: emptyObj,
getTheadFilterProps: emptyObj,
getTheadFilterTrProps: emptyObj,
getTheadFilterThProps: emptyObj,
getTbodyProps: emptyObj,
getTrGroupProps: emptyObj,
getTrProps: emptyObj,
@@ -92,7 +99,8 @@ export default {
footer: undefined,
footerClassName: '',
footerStyle: {},
getFooterProps: emptyObj
getFooterProps: emptyObj,
filterMethod: undefined
},
// Text
@@ -142,7 +150,7 @@ export default {
{'-active': loading},
className
)}
{...rest}
{...rest}
>
<div className='-loading-inner'>
{loadingText}
+165 -38
View File
@@ -26,6 +26,9 @@ export default React.createClass({
getTheadProps,
getTheadTrProps,
getTheadThProps,
getTheadFilterProps,
getTheadFilterTrProps,
getTheadFilterThProps,
getTbodyProps,
getTrGroupProps,
getTrProps,
@@ -41,11 +44,13 @@ export default React.createClass({
manual,
loadingText,
noDataText,
showFilters,
// State
loading,
pageSize,
page,
sorting,
filtering,
pages,
// Pivoting State
pivotValKey,
@@ -82,7 +87,7 @@ export default React.createClass({
const minRows = this.getMinRows()
const padRows = pages > 1 ? _.range(pageSize - pageRows.length)
: minRows ? _.range(Math.max(minRows - pageRows.length, 0))
: []
: []
const hasColumnFooter = allVisibleColumns.some(d => d.footer)
@@ -360,6 +365,142 @@ export default React.createClass({
)
}
const makeFilters = () => {
const theadFilterProps = _.splitProps(getTheadFilterProps(finalState, undefined, undefined, this))
const theadFilterTrProps = _.splitProps(getTheadFilterTrProps(finalState, undefined, undefined, this))
return (
<TheadComponent
className={classnames('-filters', theadFilterProps.className)}
style={{
...theadFilterProps.style,
minWidth: `${rowMinWidth}px`
}}
{...theadFilterProps.rest}
>
<TrComponent
className={theadFilterTrProps.className}
style={theadFilterTrProps.style}
{...theadFilterTrProps.rest}
>
{allVisibleColumns.map(makeFilter)}
</TrComponent>
</TheadComponent>
)
}
const makeFilter = (column, i) => {
const width = _.getFirstDefined(column.width, column.minWidth)
const maxWidth = _.getFirstDefined(column.width, column.maxWidth)
const theadFilterThProps = _.splitProps(getTheadFilterThProps(finalState, undefined, column, this))
const columnHeaderProps = _.splitProps(column.getHeaderProps(finalState, undefined, column, this))
const classes = [
column.headerClassName,
theadFilterThProps.className,
columnHeaderProps.className
]
const styles = {
...column.headerStyle,
...theadFilterThProps.style,
...columnHeaderProps.style
}
const rest = {
...theadFilterThProps.rest,
...columnHeaderProps.rest
}
if (column.expander) {
if (column.pivotColumns) {
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)
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)}
/>
</span>
)
if (i < column.pivotColumns.length - 1) {
pivotCols.push(<ExpanderComponent key={col.id + "-" + i}/>)
}
}
return (
<ThComponent
key={i}
className={classnames(
'rt-pivot-header',
column.sortable && '-cursor-pointer',
classes
)}
style={{
...styles,
flex: `${width} 0 auto`,
width: `${width}px`,
maxWidth: `${maxWidth}px`,
display: 'flex'
}}
{...rest}
>
{pivotCols}
</ThComponent>
)
}
return (
<ThComponent
key={i}
className={classnames(
'rt-expander-header',
classes
)}
style={{
...styles,
flex: `0 0 auto`,
width: `${expanderColumnWidth}px`
}}
{...rest}
/>
)
}
const filter = filtering.find(filter => filter.id == column.id)
return (
<ThComponent
key={i}
className={classnames(
classes
)}
style={{
...styles,
flex: `${width} 0 auto`,
width: `${width}px`,
maxWidth: `${maxWidth}px`
}}
{...rest}
>
<input type="text"
style={{
width: `100%`,
}}
value={filter ? filter.value : ""}
onChange={(event) => this.filterColumn(column, event)}
/>
</ThComponent>
)
}
const makePageRow = (row, i, path = []) => {
const rowInfo = {
row: row.__original,
@@ -443,24 +584,24 @@ export default React.createClass({
onClick={onTdClick}
>
{rowInfo.subRows ? (
<span>
<span>
<ExpanderComponent
isExpanded={isExpanded}
/>
{column && column.pivotRender ? (
<PivotCell
{...rowInfo}
value={rowInfo.rowValues[pivotValKey]}
/>
) : <span>{row[pivotValKey]} ({rowInfo.subRows.length})</span>}
{column && column.pivotRender ? (
<PivotCell
{...rowInfo}
value={rowInfo.rowValues[pivotValKey]}
/>
) : <span>{row[pivotValKey]} ({rowInfo.subRows.length})</span>}
</span>
) : SubComponent ? (
<span>
) : SubComponent ? (
<span>
<ExpanderComponent
isExpanded={isExpanded}
/>
</span>
) : null}
) : null}
</TdComponent>
)
}
@@ -526,7 +667,6 @@ export default React.createClass({
const makePadRow = (row, i) => {
const trGroupProps = getTrGroupProps(finalState, undefined, undefined, this)
const trProps = _.splitProps(getTrProps(finalState, undefined, undefined, this))
const tdProps = _.splitProps(getTdProps(finalState, undefined, undefined, this))
return (
<TrGroupComponent
key={i}
@@ -539,20 +679,6 @@ export default React.createClass({
)}
style={trProps.style || {}}
>
{SubComponent && (
<ThComponent
className={classnames(
'rt-expander-header',
tdProps.className
)}
style={{
...tdProps.style,
flex: `0 0 auto`,
width: `${expanderColumnWidth}px`
}}
{...tdProps.rest}
/>
)}
{allVisibleColumns.map((column, i2) => {
const show = typeof column.show === 'function' ? column.show() : column.show
const width = _.getFirstDefined(column.width, column.minWidth)
@@ -735,6 +861,7 @@ export default React.createClass({
>
{hasHeaderGroups ? makeHeaderGroups() : null}
{makeHeaders()}
{showFilters ? makeFilters() : null}
<TbodyComponent
className={classnames(tBodyProps.className)}
style={{
@@ -749,18 +876,18 @@ export default React.createClass({
{hasColumnFooter ? makeColumnFooters() : null}
</TableComponent>
{showPagination ? (
<PaginationComponent
{...resolvedState}
pages={pages}
canPrevious={canPrevious}
canNext={canNext}
onPageChange={this.onPageChange}
onPageSizeChange={this.onPageSizeChange}
className={paginationProps.className}
style={paginationProps.style}
{...paginationProps.rest}
/>
) : null}
<PaginationComponent
{...resolvedState}
pages={pages}
canPrevious={canPrevious}
canNext={canNext}
onPageChange={this.onPageChange}
onPageSizeChange={this.onPageSizeChange}
className={paginationProps.className}
style={paginationProps.style}
{...paginationProps.rest}
/>
) : null}
{!pageRows.length && (
<NoDataComponent
{...noDataProps}
+6
View File
@@ -22,6 +22,12 @@ $expandSize = 7px
background: alpha(black, .03)
border-bottom: 1px solid alpha(black, .05)
&.-filters
border-bottom: 1px solid alpha(black, 0.05)
.rt-th
border-right: 1px solid alpha(black, 0.02)
&.-header
box-shadow: 0 2px 15px 0px alpha(black, .15)
+17 -5
View File
@@ -11,7 +11,8 @@ export default {
page: 0,
pageSize: this.props.defaultPageSize || 10,
sorting: this.props.defaultSorting,
expandedRows: {}
expandedRows: {},
filtering: this.props.defaultFiltering
}
},
@@ -41,12 +42,19 @@ export default {
newState.sorting = newState.defaultSorting
}
if ((oldState.showFilters !== newState.showFilters) ||
(oldState.showFilters !== newState.showFilters)) {
newState.filtering = newState.defaultFiltering
}
// Props that trigger a data update
if (
oldState.data !== newState.data ||
oldState.columns !== newState.columns ||
oldState.pivotBy !== newState.pivotBy ||
oldState.sorting !== newState.sorting
oldState.sorting !== newState.sorting ||
oldState.showFilters !== newState.showFilters ||
oldState.filtering !== newState.filtering
) {
this.setStateWithData(this.getDataModel(newState))
}
@@ -55,7 +63,7 @@ export default {
setStateWithData (newState, cb) {
const oldState = this.getResolvedState()
const newResolvedState = this.getResolvedState({}, newState)
const { freezeWhenExpanded } = newResolvedState
const {freezeWhenExpanded} = newResolvedState
// Default to unfrozen state
newResolvedState.frozen = false
@@ -77,11 +85,15 @@ export default {
if (
(oldState.frozen && !newResolvedState.frozen) ||
oldState.sorting !== newResolvedState.sorting ||
oldState.filtering !== newResolvedState.filtering ||
oldState.showFilters !== newResolvedState.showFilters ||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData)
) {
// Handle collapseOnSortingChange & collapseOnDataChange
if (
(oldState.sorting !== newResolvedState.sorting && this.props.collapseOnSortingChange) ||
(oldState.filtering !== newResolvedState.filtering) ||
(oldState.showFilters !== newResolvedState.showFilters) ||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData && this.props.collapseOnDataChange)
) {
newResolvedState.expandedRows = {}
@@ -91,8 +103,8 @@ export default {
}
// Calculate pageSize all the time
if (newResolvedState.resolvedData) {
newResolvedState.pages = newResolvedState.manual ? newResolvedState.pages : Math.ceil(newResolvedState.resolvedData.length / newResolvedState.pageSize)
if (newResolvedState.sortedData) {
newResolvedState.pages = newResolvedState.manual ? newResolvedState.pages : Math.ceil(newResolvedState.sortedData.length / newResolvedState.pageSize)
}
return this.setState(newResolvedState, cb)
+63 -17
View File
@@ -192,7 +192,7 @@ export default {
// Group the rows together for this level
let groupedRows = Object.entries(
_.groupBy(rows, keys[i]))
.map(([key, value]) => {
.map(([key, value]) => {
return {
[pivotIDKey]: keys[i],
[pivotValKey]: key,
@@ -200,7 +200,7 @@ export default {
[subRowsKey]: value
}
}
)
)
// Recurse into the subRows
groupedRows = groupedRows.map(rowGroup => {
let subRows = groupRecursively(rowGroup[subRowsKey], keys, i + 1)
@@ -233,12 +233,16 @@ export default {
const {
manual,
sorting,
resolvedData
filtering,
showFilters,
defaultFilterMethod,
resolvedData,
allVisibleColumns
} = resolvedState
// Resolve the data from either manual data or sorted data
return {
sortedData: manual ? resolvedData : this.sortData(resolvedData, sorting)
sortedData: manual ? resolvedData : this.sortData(resolvedData, sorting, showFilters, filtering, defaultFilterMethod, allVisibleColumns)
}
},
@@ -251,11 +255,28 @@ export default {
getStateOrProp (key) {
return _.getFirstDefined(this.state[key], this.props[key])
},
sortData (data, sorting) {
if (!sorting.length) {
return data
sortData (data, sorting, showFilters, filtering, defaultFilterMethod, allVisibleColumns) {
let filteredData = data
if (showFilters && filtering.length) {
filteredData = filtering.reduce(
(filteredSoFar, nextFilter) => {
return filteredSoFar.filter(
(row) => {
const column = allVisibleColumns.find(x => x.id == nextFilter.id) || {}
const filterMethod = column.filterMethod || defaultFilterMethod
return filterMethod(nextFilter, row, column)
})
}
, filteredData
)
}
const sorted = _.orderBy(data, sorting.map(sort => {
if (!sorting.length) {
return filteredData
}
const sorted = _.orderBy(filteredData, sorting.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
return -Infinity
@@ -281,23 +302,23 @@ export default {
// User actions
onPageChange (page) {
const { onPageChange, collapseOnPageChange } = this.props
const {onPageChange, collapseOnPageChange} = this.props
if (onPageChange) {
return onPageChange(page)
}
const newState = { page }
const newState = {page}
if (collapseOnPageChange) {
newState.expandedRows = {}
}
this.setStateWithData(
newState
, () => {
this.fireOnChange()
})
, () => {
this.fireOnChange()
})
},
onPageSizeChange (newPageSize) {
const { onPageSizeChange } = this.props
const { pageSize, page } = this.getResolvedState()
const {onPageSizeChange} = this.props
const {pageSize, page} = this.getResolvedState()
// Normalize the page to display
const currentRow = pageSize * page
@@ -315,8 +336,8 @@ export default {
})
},
sortColumn (column, additive) {
const { sorting } = this.getResolvedState()
const { onSortingChange } = this.props
const {sorting} = this.getResolvedState()
const {onSortingChange} = this.props
if (onSortingChange) {
return onSortingChange(column, additive)
}
@@ -398,5 +419,30 @@ export default {
}, () => {
this.fireOnChange()
})
},
filterColumn (column, event) {
const {filtering} = this.getResolvedState()
const {onFilteringChange} = this.props
if (onFilteringChange) {
return onFilteringChange(column, event)
}
// Remove old filter first if it exists
const newFiltering = (filtering || []).filter(x => x.id != column.id)
if (event.target.value != "") {
newFiltering.push({
id: column.id,
value: event.target.value
})
}
this.setStateWithData({
page: 0,
filtering: newFiltering
}, () => {
this.fireOnChange()
})
}
}
+206
View File
@@ -0,0 +1,206 @@
import React from 'react'
import _ from 'lodash'
import namor from 'namor'
import CodeHighlight from './components/codeHighlight'
import ReactTable from '../src/index'
class Filtering extends React.Component {
constructor(props) {
super(props)
const data = _.map(_.range(5553), d => {
return {
firstName: namor.generate({words: 1, numLen: 0}),
lastName: namor.generate({words: 1, numLen: 0}),
age: Math.floor(Math.random() * 30)
}
})
this.state = {
tableOptions: {
loading: false,
showPagination: true,
showPageSizeOptions: true,
showPageJump: true,
collapseOnSortingChange: true,
collapseOnPageChange: true,
collapseOnDataChange: true,
freezeWhenExpanded: false,
showFilters: true
},
data: data
}
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'
}]
}]
return (
<div>
<div style={{float: "left"}}>
<h1>Table Options</h1>
<table>
<tbody>
{
Object.keys(this.state.tableOptions).map(optionKey => {
const optionValue = this.state.tableOptions[optionKey];
return (
<tr key={optionKey}>
<td>{optionKey}</td>
<td style={{paddingLeft: 10, paddingTop: 5}}>
<input type="checkbox"
name={optionKey}
checked={optionValue}
onChange={this.setTableOption}
/>
</td>
</tr>
)
})
}
</tbody>
</table>
</div>
<div className='table-wrap' style={{paddingLeft: 240}}>
<ReactTable
className='-striped -highlight'
data={this.state.data}
columns={columns}
defaultPageSize={10}
defaultFilterMethod={(filter, row) => ((row[filter.id] + "").startsWith(filter.value))}
{...this.state.tableOptions}
SubComponent={(row) => {
return (
<div style={{padding: '20px'}}>
<em>You can put any component you want here, even another React Table!</em>
<br />
<br />
<ReactTable
data={this.state.data}
columns={columns}
defaultPageSize={3}
showPagination={false}
SubComponent={(row) => {
return (
<div style={{padding: '20px'}}>
<em>It even has access to the row data: </em>
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
</div>
)
}}
/>
</div>
)
}}
/>
</div>
<div style={{textAlign: 'center'}}>
<br />
<em>Tip: Hold shift when sorting to multi-sort!</em>
</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>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>
</div>
<CodeHighlight>{() => this.getCode()}</CodeHighlight>
</div>
)
}
setTableOption(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
tableOptions: {
...this.state.tableOptions,
[name]: value
}
})
}
getCode() {
return `
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'
}]
}]
export default (
<ReactTable
data={data}
columns={columns}
defaultPageSize={10}
defaultFilterMethod={(filter, row) => ((row[filter.id] + \"\").startsWith(filter.value))}
{...otherOptions}
SubComponent={(row) => {
return (
<div style={{padding: '20px'}}>
<em>You can put any component you want here, even another React Table!</em>
<br />
<br />
<ReactTable
data={data}
columns={columns}
defaultPageSize={3}
showPagination={false}
SubComponent={(row) => {
return (
<div style={{padding: '20px'}}>
<em>It even has access to the row data: </em>
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
</div>
)
}}
/>
</div>
)
}}
/>
)
`
}
}
export default () => <Filtering/>
+6 -2
View File
@@ -33,7 +33,8 @@ export default () => {
aggregate: vals => _.round(_.mean(vals)),
render: row => {
return <span>{row.aggregated ? `${row.value} (avg)` : row.value}</span>
}
},
filterMethod: (filter, row) => (filter.value == `${row[filter.id]} (avg)`)
}, {
header: 'Visits',
accessor: 'visits',
@@ -50,6 +51,7 @@ export default () => {
defaultPageSize={10}
className='-striped -highlight'
pivotBy={['firstName', 'lastName']}
showFilters={true}
SubComponent={(row) => {
return (
<div style={{padding: '20px'}}>
@@ -104,7 +106,8 @@ const columns = [{
aggregate: vals => _.round(_.mean(vals)),
render: row => {
return <span>{row.aggregated ? \`\${row.value} (avg)\` : row.value}</span>
}
},
filterMethod: (filter, row) => (filter.value == \`\${row[filter.id]} (avg)\`)
}, {
header: 'Visits',
accessor: 'visits',
@@ -119,6 +122,7 @@ return (
defaultPageSize={10}
className='-striped -highlight'
pivotBy={['firstName', 'lastName']}
showFilters={true}
SubComponent={(row) => {
return (
<div style={{padding: '20px'}}>
+20 -6
View File
@@ -14,23 +14,34 @@ const rawData = _.map(_.range(3424), d => {
})
// Now let's mock the server. It's job is simple: use the table model to sort and return the page data
const requestData = (pageSize, page, sorting) => {
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
const sortedData = _.orderBy(rawData, sorting.map(sort => {
let filteredData = rawData;
if (filtering.length) {
filteredData = filtering.reduce(
(filteredSoFar, nextFilter) => {
return filteredSoFar.filter(
(row) => {
return (row[nextFilter.id]+"").includes(nextFilter.value)
})
}
, filteredData)
}
const sortedData = _.orderBy(filteredData, sorting.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
return -Infinity
}
return typeof row[sort.id] === 'string' ? row[sort.id].toLowerCase() : row[sort.id]
}
}), sorting.map(d => d.asc ? 'asc' : 'desc'))
}), sorting.map(d => d.desc ? 'desc' : 'asc'))
// Be sure to send back the rows to be displayed and any other pertinent information, like how many pages there are total.
const res = {
rows: sortedData.slice(pageSize * page, (pageSize * page) + pageSize),
pages: Math.ceil(rawData.length / pageSize)
pages: Math.ceil(filteredData.length / pageSize)
}
// Here we'll simulate a server response with 500ms of delay.
@@ -52,7 +63,7 @@ const ServerSide = React.createClass({
// You can set the `loading` prop of the table to true to use the built-in one or show you're own loading bar if you want.
this.setState({loading: true})
// Request the data however you want. Here, we'll use our mocked service we created earlier
requestData(state.pageSize, state.page, state.sorting)
requestData(state.pageSize, state.page, state.sorting, state.filtering)
.then((res) => {
console.log(res.rows)
// Now just get the rows of data to your React Table (and update anything else like total pages or loading)
@@ -82,6 +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={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
@@ -124,7 +136,8 @@ export default React.createClass({
Axios.post('mysite.com/data', {
pageSize: state.pageSize,
page: state.page,
sorting: state.sorting
sorting: state.sorting,
filtering: state.filtering
})
.then((res) => {
// Now update your state!
@@ -151,6 +164,7 @@ export default React.createClass({
}]}
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
defaultPageSize={10}
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
+2 -1
View File
@@ -27,7 +27,8 @@ class SubComponents extends React.Component {
collapseOnSortingChange: true,
collapseOnPageChange: true,
collapseOnDataChange: true,
freezeWhenExpanded: false
freezeWhenExpanded: false,
showFilters: false
},
data: data
}