Compare commits

..
50 Commits
Author SHA1 Message Date
Tanner Linsley a1e724c483 5.5.3 2017-04-17 12:00:29 -06:00
Tanner Linsley 741660c915 Use webpack to compile umd 2017-04-17 12:00:17 -06:00
Tanner Linsley 1f8062501f Better npm scripts for ci and publishing 2017-04-17 11:27:20 -06:00
Tanner Linsley fb51afd0ae Keep tags up to date with releases 2017-04-17 09:30:02 -06:00
Tanner Linsley a64c6a3a76 5.5.2 2017-04-17 09:29:06 -06:00
Tanner Linsley f502ef4e2b Immutablely decorate with _viewIndex
#203
2017-04-17 09:28:31 -06:00
theCmakerandTanner Linsley c58c96c84f Fixed issue #14 Default CSS overwrites the checkbox appearance if included into a custom cell. (#197) 2017-04-17 09:15:53 -06:00
Tanner Linsley 7202776c86 5.5.1 2017-04-13 10:49:15 -06:00
Mike DeVitaandGitHub 734ba48e9c Update ISSUE_TEMPLATE.md
fix the system information section to use bulleted lists
2017-04-10 10:12:04 -07:00
Alejandro BrozzoandTanner Linsley db84ecf789 Fixed code highlights (#179) 2017-04-10 10:21:31 -06:00
AlejandroandTanner Linsley 50668fe90f Avoid page refresh if same page, set NaN to current page (#178)
* Avoid page refresh if same page, set NaN to current page

* Fixed styling rule errors
2017-04-07 17:07:42 -06:00
Aaron SchwartzandGitHub 52f2c9fdac Fix descending sort being non-deterministic
Descending sort would sort differently for the same data on rerender for items that evaluated to the same value.
2017-04-06 15:25:56 -07:00
Tanner Linsley 3b39f523be 5.5.0 2017-04-05 18:51:31 -06:00
Aaron SchwartzandTanner Linsley 9d85981c5b Resize columns (#170)
* Add column resizing for non pivot columns.

* Fixing resizing UI issues and mobile functionality.

* Remove calling onChange during resize events so that server example doesn't refetch data every time a column resizes.
2017-04-05 17:47:55 -06:00
Eric AbbottandTanner Linsley b779a98d7a Change precedence in 'getResolvedState' (#166)
* Change precedence in 'getResolvedState'
  * Previously existing props would overwrite passed in state
  * Now passed in state gets precedence

* added a controlled table example to storybook
2017-04-05 01:10:17 -06:00
Jolyon RussandTanner Linsley 1f4c9afd2b Updated README - wrapped example in render function (#168) 2017-04-04 13:24:03 -06:00
Aaron SchwartzandTanner Linsley cef7fbd780 Add filterRender column option to customize the filter that is shown (#162)
* Add filterRender column option to completely customize the filter that is shown

* Refactor filterRender as a defaultProp.
2017-03-31 15:41:57 -06:00
Tanner Linsley e8ebf7c780 5.4.1 2017-03-30 17:03:47 -06:00
Aaron SchwartzandTanner Linsley ac20220d55 Set page to max page available if data changes and has less pages than before (#160)
* 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.

* Set page to max page available if data changes and has less pages than before.

Closes #159
2017-03-30 17:02:39 -06:00
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
Ryan PalermoandTanner Linsley 4c11248c69 switch -sort-asc and -sort-desc in ternary (#138) 2017-03-23 13:49:03 -06:00
Tanner Linsley 784311bcd6 Remove .DS_Store
Closes #126
2017-03-14 12:18:46 -06:00
Deepak Kumar JainandTanner Linsley 7b232cd6b4 Updated the package.json (#124) 2017-03-12 09:29:32 -06:00
MishaandTanner Linsley 533bea77b6 use button type button in pagination to prevent form submitting if using table inside of the form (#122) 2017-03-09 09:14:49 -07:00
MishaandTanner Linsley 218cf42bb2 removed form tag from pagination component to avoid nested forms error (#119) 2017-03-09 07:34:44 -07:00
Wojtek BażantandTanner Linsley 62287d1641 Add extra empty line to get the accessors table to show up nicely on github's main page (#121) 2017-03-09 07:34:22 -07:00
Ryan PalermoandTanner Linsley 10dc68acc1 use Object.getPrototypeOf instead of .prototype (#117) 2017-03-08 11:43:29 -07:00
Michael BridgeandTanner Linsley bd82468852 Pass relevant attributes to footer JSX (#116) 2017-03-08 11:42:12 -07:00
Tanner Linsley 6d28bb2c4d 5.3.4 2017-03-01 15:50:43 -07:00
Tanner Linsley 9e95b331df Feature: Changing defaultSorting now updates the sorting model 2017-03-01 15:50:36 -07:00
TacticalCodingandTanner Linsley f296b075fe Rename CollapseOnPageChange to CollapseOnDataChange (#100)
* Rename CollapseOnPageChange to CollapseOnDataChange to more accurately reflect its purpose.

* Add support for collapseOnPageChange.

* Fix collapseOnPageChange efficiency.
2017-02-27 16:05:07 -07:00
Tanner Linsley a948a9a41e 5.3.3 2017-02-27 10:01:06 -07:00
atlantehandTanner Linsley 08dbe38ced fix number of callback parameters and alignment (#95) 2017-02-27 08:15:45 -07:00
Daniel WinterandTanner Linsley 884058b459 added missing named export for ReactTableDefaults (#93) 2017-02-19 07:09:29 -07:00
Tanner Linsley ee37ad7101 Update Docs 2017-02-16 15:15:47 -07:00
Tanner Linsley 6b384f2a90 5.3.2 2017-02-16 14:02:30 -07:00
Tanner Linsley 782e9efdfb Fix NoData from always showing 2017-02-16 14:02:21 -07:00
Tanner Linsley ff952de830 5.3.1 2017-02-16 12:40:48 -07:00
Tanner Linsley 149b11674b Docs Update 2017-02-16 12:40:43 -07:00
Tanner Linsley 8a362e4d04 Rollback on table partials to fix mounting/unmounting issues 2017-02-16 12:40:12 -07:00
Tanner Linsley 265a5c8753 5.3.0 2017-02-16 10:41:52 -07:00
Tanner Linsley 5155ebad72 Added more customization for functional rendering 2017-02-16 10:41:52 -07:00
Tanner Linsley d7a58450b7 Added support for Column Footers 2017-02-16 10:41:52 -07:00
avnunnandTanner Linsley bb2527e280 Added documentation for column accessors (#82) 2017-02-15 17:39:01 -07:00
36 changed files with 2261 additions and 718 deletions
Vendored
BIN
View File
Binary file not shown.
+2
View File
@@ -3,3 +3,5 @@ lib/
react-table.js
react-table.css
*.log
.idea
.DS_Store
+6
View File
@@ -23,6 +23,9 @@ import OneHundredKRows from '../stories/OneHundredKRows.js'
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'
import ControlledTable from '../stories/ControlledTable.js'
//
configure(() => {
storiesOf('1. Docs')
@@ -51,4 +54,7 @@ configure(() => {
.add('Functional Rendering', FunctionalRendering)
.add('Custom Expander Position', CustomExpanderPosition)
.add('Custom "No Data" Text', NoDataText)
.add('Footers', Footers)
.add('Custom Filtering', Filtering)
.add('Controlled Component', ControlledTable)
}, module)
+12
View File
@@ -4,3 +4,15 @@ body {
font-family: 'Open Sans', sans-serif;
font-weight: 300;
}
h1 {
font-size: 24px;
font-weight: bold;
margin-bottom: 15px;
margin-top: 5px;
}
p {
margin-bottom: 15px;
line-height: 22px;
}
+4
View File
@@ -1,3 +1,7 @@
body{
padding: 20px;
}
strong {
font-weight: bold
}
+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:**
+179 -87
View File
@@ -24,6 +24,9 @@
<a href="https://twitter.com/tannerlinsley" target="\_parent">
<img alt="" src="https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow" />
</a>
<a href="https://cash.me/$tannerlinsley" target="\_parent">
<img alt="" src="https://img.shields.io/badge/%24-Donate-brightgreen.svg" />
</a>
## Features
@@ -44,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)
@@ -53,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)
@@ -86,36 +90,39 @@ import 'react-table/react-table.css'
```javascript
import ReactTable from 'react-table'
const data = [{
name: 'Tanner Linsley',
age: 26,
friend: {
name: 'Jason Maurer',
age: 23,
}
},{
...
}]
render() {
const data = [{
name: 'Tanner Linsley',
age: 26,
friend: {
name: 'Jason Maurer',
age: 23,
}
},{
...
}]
const columns = [{
header: 'Name',
accessor: 'name' // String-based value accessors !
}, {
header: 'Age',
accessor: 'age',
render: props => <span className='number'>{props.value}</span> // Custom cell components!
}, {
header: 'Friend Name',
accessor: d => d.friend.name // Custom value accessors!
}, {
header: props => <span>Friend Age</span>, // Custom header components!
accessor: 'friend.age'
}]
const columns = [{
header: 'Name',
accessor: 'name' // String-based value accessors!
}, {
header: 'Age',
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!
}, {
header: props => <span>Friend Age</span>, // Custom header components!
accessor: 'friend.age'
}]
<ReactTable
data={data}
columns={columns}
/>
<ReactTable
data={data}
columns={columns}
/>
}
```
## Data
@@ -135,21 +142,32 @@ These are all of the available props (and their default values) for the main `<R
defaultPageSize: 20,
showPageJump: true,
expanderColumnWidth: 35,
collapseOnSortingChange: false,
collapseOnSortingChange: true,
collapseOnPageChange: true,
collapseOnDataChange: true,
freezeWhenExpanded: false,
defaultSorting: [],
showFilters: false,
defaultFiltering: [],
defaultFilterMethod: (filter, row, column) => {
const id = filter.pivotId || filter.id
return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true
},
resizable: true,
defaultResizing: [],
// Controlled State Overrides (see Fully Controlled Component section)
page: undefined,
pageSize: undefined,
sorting: undefined
sorting: undefined,
// Controlled State Callbacks
onExpandSubComponent: undefined,
onPageChange: undefined,
onPageSizeChange: undefined,
onSortingChange: undefined,
onFilteringChange: undefined,
onResize: undefined,
// Pivoting
pivotBy: undefined,
@@ -180,14 +198,21 @@ These are all of the available props (and their default values) for the main `<R
getTheadProps: () => ({}),
getTheadTrProps: () => ({}),
getTheadThProps: () => ({}),
getTheadFilterProps: () => ({}),
getTheadFilterTrProps: () => ({}),
getTheadFilterThProps: () => ({}),
getTbodyProps: () => ({}),
getTrGroupProps: () => ({}),
getTrProps: () => ({}),
getThProps: () => ({}),
getTdProps: () => ({}),
getTfootProps: () => ({}),
getTfootTrProps: () => ({}),
getTfootThProps: () => ({}),
getPaginationProps: () => ({}),
getLoadingProps: () => ({}),
getNoDataProps: () => ({}),
getResizerProps: () => ({}),
// Global Column Defaults
column: {
@@ -204,6 +229,22 @@ These are all of the available props (and their default values) for the main `<R
headerClassName: '',
headerStyle: {},
getHeaderProps: () => ({})
// Footers only
footer: undefined,
footerClassName: '',
footerStyle: {},
getFooterProps: () => ({}),
filterMethod: undefined,
hideFilter: false,
filterRender: ({filter, onFilterChange}) => (
<input type='text'
style={{
width: '100%'
}}
value={filter ? filter.value : ''}
onChange={(event) => onFilterChange(event.target.value)}
/>
)
},
// Text
@@ -245,16 +286,16 @@ Or just define them as props
```javascript
[{
// General
accessor: 'propertyName' or Accessor eg. (row) => row.propertyName,
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
@@ -268,16 +309,53 @@ Or just define them as props
// viewIndex == the index of the row in the current page
// Header & HeaderGroup Options
header: 'Header Name' or JSX eg. ({data, column}) => <div>Header Name</div>,
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
// Header Groups only
columns: [...] // See Header Groups section below
columns: [...], // See Header Groups section below
// Footer
footer: 'Footer Name' or JSX eg. ({data, column}) => <div>Footer 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
// 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
hideFilter: false, // If `showFilters` is set on the table, this option will let you selectively hide the filter on a particular row
filterRender: JSX // eg. ({filter, onFilterChange}) => <select onChange={event => onFilterChange(event.target.value)} value={filter ? filter.value : ''}></select> // The value passed to onFilterChange will be the value passed to filter.value of the filterMethod
}]
```
## Accessors
Accessors are functions that return the value to populate the row's value for the column.
This lets the render function not have to worry about accessing the correct data, the value is automatically populated in it's props.
If a `string` or `array` is passed the default accessor is used.
The default accessor will parse the input into an array and recursively flatten it.
Any values that contain a dot (`.`) will be split.
Any values that contain bracket (`[]`) will be split.
This array is then used as the path to the value to return.
("$" is the placeholder value that would be returned by the default accessor)
| value | path | data |
|--------------|-----------------|------------------------|
| "a" | ["a"] | {"a": $} |
| "a.b" | ["a", "b"] | {"a": {"b": $}} |
| "a[0]" | ["a", "0"] | {"a": [$]} |
| ["a.b", "c"] | ["a", "b", "c"] | {"a": {"b": {"c": $}}} |
*NOTE*
If your data has a field/key with a dot (`.`) you will need to supply a custom accessor.
## Column Header Groups
To group columns with another header column, just nest your columns in a header column. Header columns utilize the same header properties as regular columns.
```javascript
@@ -297,8 +375,8 @@ const columns = [{
}]
```
## Custom Cell and Header Rendering
You can use any react component or JSX to display column headers or cells. Any component you use will be passed the following props:
## Custom Cell, Header and Footer Rendering
You can use any react component or JSX to display content in column headers, cells and footers. Any component you use will be passed the following props (if available):
- `row` - Original row from your data
- `rowValues` - The post-accessed values from the original row
- `index` - The index of the row
@@ -340,17 +418,19 @@ const columns = [{
```
## Styles
React-table ships with a minimal and clean stylesheet to get you on your feet quickly. It's located at `react-table/react-table.css`.
- React-table ships with a minimal and clean stylesheet to get you on your feet quickly.
- The stylesheet is located at `react-table/react-table.css`.
- There are countless ways to import a stylesheet. If you have questions on how to do so, consult the documentation of your build system.
#### Built-in Styles
#### Classes
- Adding a `-striped` className to ReactTable will slightly color odd numbered rows for legibility
- Adding a `-highlight` className to ReactTable will highlight any row as you hover over it
#### CSS Styles
#### CSS
We think the default styles looks great! But, if you prefer a more custom look, all of the included styles are easily overridable. Every single component contains a unique class that makes it super easy to customize. Just go for it!
#### JS Styles
Every single react-table element and `get[ComponentName]Props` callback support classes (powered by `classname` and js styles.
Every single react-table element and `get[ComponentName]Props` callback supports `classname` and `style` props.
## Custom Props
@@ -373,14 +453,16 @@ Every single built-in component's props can be dynamically extended using any on
getTdProps={fn}
getPaginationProps={fn}
getLoadingProps={fn}
getNoDataProps: {fn},
getResizerProps: {fn}
/>
```
These callbacks are executed with each render of the element with three parameters:
1. Table State
1. RowInfo (undefined if not applicable)
1. Column (undefined if not applicable)
1. React Table Instance
These callbacks are executed with each render of the element with four parameters:
1. Table State
2. RowInfo (undefined if not applicable)
3. Column (undefined if not applicable)
4. React Table Instance
This makes it extremely easy to add, say... a row click callback!
```javascript
@@ -488,7 +570,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
@@ -511,7 +593,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
@@ -557,76 +640,85 @@ 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, value) => {...}} // Called when a user enters a value into a filter input field or the value passed to the onFilterChange handler by the filterRender option.
onResize={(column, event, isTouch) => {...}} // Called when a user clicks on a resizing component (the right edge of a column header)
/>
```
## Functional Rendering
Possibly one of the coolest features of React-Table is its ability to expose internal state for custom render logic. The easiest way to do this is to optionally pass a function as a child of `<ReactTable />`.
Possibly one of the coolest features of React-Table is its ability to expose internal components and state for custom render logic. The easiest way to do this is to pass a function as the child of `<ReactTable />`.
The function you pass will be called with the following items:
- Fully-resolved state of the table
- The standard table generator
- A function that returns the standard table component
- The instance of the component
You can then return any JSX or react you want! This turns out to be perfect for:
- Accessing the internal state of the table before rendering the table
- Decorating the table with more UI
- Building your own 100% custom display logic, while utilizing the state and methods of the table component
- Accessing the internal state of the table without a `ref`
- Decorating the table or extending it with your own UI
- Building your own custom display logic
Example:
Accessing internal state and wrapping with more UI:
```javascript
<ReactTable
columns={columns}
data={data}
...
columns={columns}
>
{(state, makeTable, instance) => {
// Now you have full access to the state of the table!
state.decoratedColumns === [...] // all of the columns (with id's and meta)
state.visibleColumns === [...] // all of the columns (with id's and meta)
state.visibleColumns === [...] // all of the columns (with id's and meta)
// etc.
// `makeTable` is a function that returns the standard table markup
return makeTable()
// So add some decoration!
return (
<div>
<customPivotBySelect />
<customColumnHideShow />
<customAnything />
<div style={{
background: '#ffcf00',
borderRadius: '5px',
overflow: 'hidden',
padding: '5px'
}}>
<pre><code>state.allVisibleColumns === {JSON.stringify(state.allVisibleColumns, null, 4)}</code></pre>
{makeTable()}
</div>
)
// The possibilities are endless!!!
}}
</ReactTable>
```
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.
To completely override the filter that is shown, you can set the `filterRender` column option. Using this option you can specify the JSX that is shown. The option is passed an `onFilterChange` 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 <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
// Change the global default
import { ReactTableDefaults } from 'react-table'
Object.assign(ReactTableDefaults, {
TableComponent: Component,
TheadComponent: Component,
TbodyComponent: Component,
TrGroupComponent: Component,
TrComponent: Component,
ThComponent: Component,
TdComponent: Component,
PaginationComponent: Component,
PreviousComponent: Component,
NextComponent: Component,
LoadingComponent: Component,
ExpanderComponent: Component
TableComponent: component,
TheadComponent: component,
TbodyComponent: component,
TrGroupComponent: component,
TrComponent: component,
ThComponent: component
TdComponent: component,
TfootComponent: component,
ExpanderComponent: component,
PaginationComponent: component,
PreviousComponent: undefined,
NextComponent: undefined,
LoadingComponent: component,
NoDataComponent: component,
ResizerComponent: component
})
// Or change per instance
+1 -1
View File
@@ -16,7 +16,7 @@
<body>
<div id="root"></div>
<div id="error-display"></div>
<script src="static/preview.1dfdb2ebd0085aa535c8.bundle.js"></script>
<script src="static/preview.0cd0bb2bf09f3220bea7.bundle.js"></script>
</body>
</html>
+1 -1
View File
@@ -38,7 +38,7 @@
</head>
<body style="margin: 0;">
<div id="root"></div>
<script src="static/manager.d3baea6e6f08c3c0279c.bundle.js"></script>
<script src="static/manager.064c8fe6b78907f0a142.bundle.js"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"version":3,"file":"static/manager.064c8fe6b78907f0a142.bundle.js","sources":["webpack:///static/manager.064c8fe6b78907f0a142.bundle.js"],"mappings":"AAAA;AAkuDA;AA84DA;AAy8DA;AA00DA;AAsyEA;AA89CA;AA+rDA;AAsiDA;AAg6DA;AA2nDA;AA++CA;AAkvDA;AAsnEA;AA2oDA;AAivCA;AA+nDA;AAkpDA;AA6lEA;AAs4DA;AAquDA;AA+pDA;AAqxDA;AAsrDA;AA4yDA;AA88GA;AAj6CA;AAopGA;AAsuFA;AA+zEA;AAtMA;AAizEA","sourceRoot":""}
@@ -1 +0,0 @@
{"version":3,"file":"static/manager.d3baea6e6f08c3c0279c.bundle.js","sources":["webpack:///static/manager.d3baea6e6f08c3c0279c.bundle.js"],"mappings":"AAAA;AAkuDA;AA84DA;AA28DA;AA00DA;AAsyEA;AA89CA;AA8rDA;AAsiDA;AAg6DA;AA2nDA;AA++CA;AAkvDA;AAsnEA;AA2oDA;AAivCA;AA+nDA;AAkpDA;AA6lEA;AAs4DA;AAquDA;AA+pDA;AAqxDA;AAsrDA;AAyyDA;AA88GA;AAj6CA;AAopGA;AAsuFA;AA+zEA;AAtMA;AAizEA","sourceRoot":""}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"version":3,"file":"static/preview.0cd0bb2bf09f3220bea7.bundle.js","sources":["webpack:///static/preview.0cd0bb2bf09f3220bea7.bundle.js"],"mappings":"AAAA;AAkuDA;AA62DA;AAwtFA;AAsiFA;AA0uOA;AAgmGA;AA6rDA;AA0iEA;AA0xDA;AA25DA;AAmqDA;AA0vDA;AA+9CA;AAg5DA;AAwoDA;AAk7CA;AA2pDA;AAmjEA;AA27DA;AAghCA;AA4vDA;AA8kDA;AAwpEA;AAk3DA;AAy0DA;AA8xCA;AAu3EA;AAi4GA;AAwoDA;AAk+CA;AA+oCA;AAutCA;AA4jDA;AAwcA;AA+jFA","sourceRoot":""}
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
{"version":3,"file":"static/preview.1dfdb2ebd0085aa535c8.bundle.js","sources":["webpack:///static/preview.1dfdb2ebd0085aa535c8.bundle.js"],"mappings":"AAAA;AAkuDA;AAu+CA;AA0+FA;AAmpFA;AAkxLA;AA7sBA;AAojOA;AAkjEA;AAivDA;AAy7DA;AAqlDA;AAmvDA;AAu8CA;AA6+DA;AAwlDA;AA2+CA;AAimDA;AA29DA;AA04DA;AAsxCA;AAynDA;AAurDA;AAgiEA;AA03DA;AAwwCA;AA8tDA;AA02FA;AA+tGA;AAy+CA;AAuzCA;AAkuCA;AAikDA;AA8dA;AA+jFA","sourceRoot":""}
+8 -10
View File
@@ -1,6 +1,6 @@
{
"name": "react-table",
"version": "5.2.0",
"version": "5.5.3",
"description": "A fast, lightweight, opinionated table and datagrid built on React",
"license": "MIT",
"homepage": "https://github.com/tannerlinsley/react-table#readme",
@@ -24,14 +24,16 @@
],
"scripts": {
"build:node": "babel src --out-dir lib --source-maps inline",
"build:css": "rm -rf react-table.css && stylus src/index.styl --compress -o react-table.css && yarn run css:autoprefix",
"build:css": "rimraf react-table.css && stylus src/index.styl --compress -o react-table.css && yarn run css:autoprefix",
"css:autoprefix": "postcss --use autoprefixer react-table.css -r",
"watch": "npm-run-all --parallel watch:*",
"watch:node": "onchange 'src/**/*.js' -i -- npm run build:node",
"watch:css": "onchange 'src/**/*.styl' -i -- npm run build:css",
"test": "standard",
"umd": "rm -rf react-table.js && browserify lib/index.js -s reactTable -x react -t babelify -g uglifyify -o react-table.js",
"prepublish": "npm-run-all build:* && npm run umd",
"umd": "rimraf react-table.js && webpack --config umd.webpack.js",
"build": "npm-run-all build:*",
"prepublish": "npm run build && npm run umd",
"postpublish": "git push --tags",
"storybook": "start-storybook -p 8000 -c .storybook",
"docs": "build-storybook -o docs && cp .storybook/CNAME docs/CNAME"
},
@@ -49,8 +51,6 @@
"babel-preset-es2015": "6.14.0",
"babel-preset-react": "6.11.1",
"babel-preset-stage-2": "6.13.0",
"babelify": "^7.3.0",
"browserify": "13.1.0",
"github-markdown-css": "^2.4.1",
"html-loader": "^0.4.4",
"markdown-loader": "^0.1.7",
@@ -61,13 +61,11 @@
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-json-tree": "^0.10.1",
"rimraf": "^2.6.1",
"standard": "^8.0.0",
"storybook": "^0.0.0",
"stylus": "^0.54.5",
"uglifyify": "3.0.3"
},
"browserify-shim": {
"react": "global:React"
"webpack": "^2.4.1"
},
"standard": {
"parser": "babel-eslint",
Binary file not shown.
+179
View File
@@ -0,0 +1,179 @@
import React from 'react'
import classnames from 'classnames'
//
import _ from './utils'
import Pagination from './pagination'
const emptyObj = () => ({})
export default {
// General
data: [],
loading: false,
showPagination: true,
showPageSizeOptions: true,
pageSizeOptions: [5, 10, 20, 25, 50, 100],
defaultPageSize: 20,
showPageJump: true,
expanderColumnWidth: 35,
collapseOnSortingChange: true,
collapseOnPageChange: true,
collapseOnDataChange: true,
freezeWhenExpanded: false,
defaultSorting: [],
showFilters: false,
defaultFiltering: [],
defaultFilterMethod: (filter, row, column) => {
const id = filter.pivotId || filter.id
return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true
},
resizable: true,
defaultResizing: [],
// Controlled State Overrides
// page: undefined,
// pageSize: undefined,
// sorting: undefined,
// Controlled State Callbacks
onExpandSubComponent: undefined,
onPageChange: undefined,
onPageSizeChange: undefined,
onSortingChange: undefined,
onFilteringChange: undefined,
onResize: undefined,
// Pivoting
pivotBy: undefined,
pivotColumnWidth: 200,
pivotValKey: '_pivotVal',
pivotIDKey: '_pivotID',
subRowsKey: '_subRows',
// Pivoting State Overrides
// expandedRows: {},
// Pivoting State Callbacks
onExpandRow: undefined,
// General Callbacks
onChange: () => null,
// Classes
className: '',
style: {},
// Component decorators
getProps: emptyObj,
getTableProps: emptyObj,
getTheadGroupProps: emptyObj,
getTheadGroupTrProps: emptyObj,
getTheadGroupThProps: emptyObj,
getTheadProps: emptyObj,
getTheadTrProps: emptyObj,
getTheadThProps: emptyObj,
getTheadFilterProps: emptyObj,
getTheadFilterTrProps: emptyObj,
getTheadFilterThProps: emptyObj,
getTbodyProps: emptyObj,
getTrGroupProps: emptyObj,
getTrProps: emptyObj,
getTdProps: emptyObj,
getTfootProps: emptyObj,
getTfootTrProps: emptyObj,
getTfootTdProps: emptyObj,
getPaginationProps: emptyObj,
getLoadingProps: emptyObj,
getNoDataProps: emptyObj,
getResizerProps: emptyObj,
// Global Column Defaults
column: {
sortable: true,
show: true,
minWidth: 100,
// Cells only
render: undefined,
className: '',
style: {},
getProps: emptyObj,
// Headers only
header: undefined,
headerClassName: '',
headerStyle: {},
getHeaderProps: emptyObj,
// Footers only
footer: undefined,
footerClassName: '',
footerStyle: {},
getFooterProps: emptyObj,
filterMethod: undefined,
hideFilter: false,
filterRender: ({filter, onFilterChange}) => (
<input type='text'
style={{
width: '100%'
}}
value={filter ? filter.value : ''}
onChange={(event) => onFilterChange(event.target.value)}
/>
)
},
// Text
previousText: 'Previous',
nextText: 'Next',
loadingText: 'Loading...',
noDataText: 'No rows found',
pageText: 'Page',
ofText: 'of',
rowsText: 'rows',
// Components
TableComponent: _.makeTemplateComponent('rt-table'),
TheadComponent: _.makeTemplateComponent('rt-thead'),
TbodyComponent: _.makeTemplateComponent('rt-tbody'),
TrGroupComponent: _.makeTemplateComponent('rt-tr-group'),
TrComponent: _.makeTemplateComponent('rt-tr'),
ThComponent: ({toggleSort, className, children, ...rest}) => {
return (
<div
className={classnames(className, 'rt-th')}
onClick={e => {
toggleSort && toggleSort(e)
}}
{...rest}
>
{children}
</div>
)
},
TdComponent: _.makeTemplateComponent('rt-td'),
TfootComponent: _.makeTemplateComponent('rt-tfoot'),
ExpanderComponent: ({isExpanded, ...rest}) => {
return (
<div
className={classnames('rt-expander', isExpanded && '-open')}
{...rest}
>&bull;</div>
)
},
PaginationComponent: Pagination,
PreviousComponent: undefined,
NextComponent: undefined,
LoadingComponent: ({className, loading, loadingText, ...rest}) => (
<div className={classnames(
'-loading',
{'-active': loading},
className
)}
{...rest}
>
<div className='-loading-inner'>
{loadingText}
</div>
</div>
),
NoDataComponent: _.makeTemplateComponent('rt-noData'),
ResizerComponent: _.makeTemplateComponent('rt-resizer')
}
+416 -375
View File
@@ -2,257 +2,15 @@ import React from 'react'
import classnames from 'classnames'
//
import _ from './utils'
import lifecycle from './lifecycle'
import methods from './methods'
import defaults from './defaultProps'
import componentMethods from './componentMethods'
import Pagination from './pagination'
const emptyObj = () => ({})
export const ReactTableDefaults = {
// General
data: [],
loading: false,
showPagination: true,
showPageSizeOptions: true,
pageSizeOptions: [5, 10, 20, 25, 50, 100],
defaultPageSize: 20,
showPageJump: true,
expanderColumnWidth: 35,
collapseOnSortingChange: false,
collapseOnPageChange: true,
freezeWhenExpanded: false,
defaultSorting: [],
// Controlled State Overrides
// page: undefined,
// pageSize: undefined,
// sorting: undefined,
// Controlled State Callbacks
onExpandSubComponent: undefined,
onPageChange: undefined,
onPageSizeChange: undefined,
onSortingChange: undefined,
// Pivoting
pivotBy: undefined,
pivotColumnWidth: 200,
pivotValKey: '_pivotVal',
pivotIDKey: '_pivotID',
subRowsKey: '_subRows',
// Pivoting State Overrides
// expandedRows: {},
// Pivoting State Callbacks
onExpandRow: undefined,
// General Callbacks
onChange: () => null,
// Classes
className: '',
style: {},
// Component decorators
getProps: emptyObj,
getTableProps: emptyObj,
getTheadGroupProps: emptyObj,
getTheadGroupTrProps: emptyObj,
getTheadGroupThProps: emptyObj,
getTheadProps: emptyObj,
getTheadTrProps: emptyObj,
getTheadThProps: emptyObj,
getTbodyProps: emptyObj,
getTrGroupProps: emptyObj,
getTrProps: emptyObj,
getThProps: emptyObj,
getTdProps: emptyObj,
getPaginationProps: emptyObj,
getLoadingProps: emptyObj,
getNoDataProps: emptyObj,
// Global Column Defaults
column: {
sortable: true,
show: true,
minWidth: 100,
// Cells only
render: undefined,
className: '',
style: {},
getProps: () => ({}),
// Headers only
header: undefined,
headerClassName: '',
headerStyle: {},
getHeaderProps: () => ({})
},
// Text
previousText: 'Previous',
nextText: 'Next',
loadingText: 'Loading...',
noDataText: 'No rows found',
pageText: 'Page',
ofText: 'of',
rowsText: 'rows',
// Components
TableComponent: _.makeTemplateComponent('rt-table'),
TheadComponent: _.makeTemplateComponent('rt-thead'),
TbodyComponent: _.makeTemplateComponent('rt-tbody'),
TrGroupComponent: _.makeTemplateComponent('rt-tr-group'),
TrComponent: _.makeTemplateComponent('rt-tr'),
ThComponent: ({toggleSort, className, children, ...rest}) => {
return (
<div
className={classnames(className, 'rt-th')}
onClick={e => {
toggleSort && toggleSort(e)
}}
{...rest}
>
{children}
</div>
)
},
TdComponent: _.makeTemplateComponent('rt-td'),
ExpanderComponent: ({isExpanded, ...rest}) => {
return (
<div
className={classnames('rt-expander', isExpanded && '-open')}
{...rest}
>&bull;</div>
)
},
PaginationComponent: Pagination,
PreviousComponent: undefined,
NextComponent: undefined,
LoadingComponent: ({className, loading, loadingText, ...rest}) => (
<div className={classnames(
'-loading',
{'-active': loading},
className
)}
{...rest}
>
<div className='-loading-inner'>
{loadingText}
</div>
</div>
),
NoDataComponent: _.makeTemplateComponent('rt-noData')
}
export const ReactTableDefaults = defaults
export default React.createClass({
getDefaultProps () {
return ReactTableDefaults
},
getInitialState () {
return {
page: 0,
pageSize: this.props.defaultPageSize || 10,
sorting: this.props.defaultSorting,
expandedRows: {}
}
},
getResolvedState (props, state) {
const resolvedState = {
..._.compactObject(this.state),
..._.compactObject(state),
..._.compactObject(this.props),
..._.compactObject(props)
}
return resolvedState
},
componentWillMount () {
this.setStateWithData(this.getDataModel())
},
componentDidMount () {
this.fireOnChange()
},
componentWillReceiveProps (nextProps, nextState) {
const oldState = this.getResolvedState()
const newState = this.getResolvedState(nextProps, nextState)
// Props that trigger a data update
if (
oldState.data !== newState.data ||
oldState.columns !== newState.columns ||
oldState.pivotBy !== newState.pivotBy ||
oldState.sorting !== newState.sorting
) {
this.setStateWithData(this.getDataModel(nextProps, nextState))
}
},
setStateWithData (newState, cb) {
const oldState = this.getResolvedState()
const newResolvedState = this.getResolvedState({}, newState)
const { freezeWhenExpanded } = newResolvedState
// Default to unfrozen state
newResolvedState.frozen = false
// If freezeWhenExpanded is set, check for frozen conditions
if (freezeWhenExpanded) {
// if any rows are expanded, freeze the existing data and sorting
const keys = Object.keys(newResolvedState.expandedRows)
for (var i = 0; i < keys.length; i++) {
if (newResolvedState.expandedRows[keys[i]]) {
newResolvedState.frozen = true
break
}
}
}
// If the data isn't frozen and either the data or
// sorting model has changed, update the data
if (
(oldState.frozen && !newResolvedState.frozen) ||
oldState.sorting !== newResolvedState.sorting ||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData)
) {
// Handle collapseOnSortingChange & collapseOnPageChange
if (
(oldState.sorting !== newResolvedState.sorting && this.props.collapseOnSortingChange) ||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData && this.props.collapseOnPageChange)
) {
newResolvedState.expandedRows = {}
}
Object.assign(newResolvedState, this.getSortedData(newResolvedState))
}
// Calculate pageSize all the time
if (newResolvedState.resolvedData) {
newResolvedState.pages = newResolvedState.manual ? newResolvedState.pages : Math.ceil(newResolvedState.resolvedData.length / newResolvedState.pageSize)
}
return this.setState(newResolvedState, cb)
},
shouldComponentUpdate (nextProps, nextState) {
const oldState = this.getResolvedState()
const newState = this.getResolvedState(nextProps, nextState)
// State changes that trigger a render
if (
oldState.sortedData !== newState.sortedData ||
oldState.page !== newState.page ||
oldState.pageSize !== newState.pageSize ||
oldState.expandedRows !== newState.expandedRows
) {
return true
}
return false
},
...lifecycle,
...methods,
render () {
const resolvedState = this.getResolvedState()
@@ -268,24 +26,34 @@ export default React.createClass({
getTheadProps,
getTheadTrProps,
getTheadThProps,
getTheadFilterProps,
getTheadFilterTrProps,
getTheadFilterThProps,
getTbodyProps,
getTrGroupProps,
getTrProps,
getThProps,
getTdProps,
getTfootProps,
getTfootTrProps,
getTfootTdProps,
getPaginationProps,
getLoadingProps,
getNoDataProps,
getResizerProps,
showPagination,
expanderColumnWidth,
manual,
loadingText,
noDataText,
showFilters,
resizable,
// State
loading,
pageSize,
page,
sorting,
filtering,
resizing,
pages,
// Pivoting State
pivotValKey,
@@ -300,11 +68,13 @@ export default React.createClass({
TrComponent,
ThComponent,
TdComponent,
TfootComponent,
ExpanderComponent,
PaginationComponent,
LoadingComponent,
SubComponent,
NoDataComponent,
ResizerComponent,
// Data model
resolvedData,
allVisibleColumns,
@@ -314,36 +84,42 @@ export default React.createClass({
sortedData
} = resolvedState
// Determine the flex percentage for each column
// const columnPercentage = 100 / allVisibleColumns.length
// Pagination
const startRow = pageSize * page
const endRow = startRow + pageSize
const pageRows = manual ? resolvedData : sortedData.slice(startRow, endRow)
let 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)
const recurseRowsViewIndex = (rows, path = [], index = -1) => {
rows.forEach((row, i) => {
index++
row._viewIndex = index
const newPath = path.concat([i])
if (row[subRowsKey] && _.get(expandedRows, newPath)) {
index = recurseRowsViewIndex(row[subRowsKey], newPath, index)
}
})
return index
return [
rows.map((row, i) => {
index++
const rowWithViewIndex = {
...row,
_viewIndex: index
}
const newPath = path.concat([i])
if (rowWithViewIndex[subRowsKey] && _.get(expandedRows, newPath)) {
[rowWithViewIndex[subRowsKey], index] = recurseRowsViewIndex(rowWithViewIndex[subRowsKey], newPath, index)
}
return rowWithViewIndex
}),
index
]
}
recurseRowsViewIndex(pageRows)
[pageRows] = recurseRowsViewIndex(pageRows)
const canPrevious = page > 0
const canNext = page + 1 < pages
const rowMinWidth = _.sum(allVisibleColumns.map(d => _.getFirstDefined(d.width, d.minWidth)))
const rowMinWidth = _.sum(allVisibleColumns.map(d => {
const resized = resizing.find(x => x.id === d.id) || {}
return _.getFirstDefined(resized.value, d.width, d.minWidth)
}))
let rowIndex = -1
@@ -354,6 +130,7 @@ export default React.createClass({
pageRows,
minRows,
padRows,
hasColumnFooter,
canPrevious,
canNext,
rowMinWidth
@@ -385,9 +162,18 @@ export default React.createClass({
}
const makeHeaderGroup = (column, i) => {
const flex = _.sum(column.columns.map(d => d.width ? 0 : d.minWidth))
const width = _.sum(column.columns.map(d => _.getFirstDefined(d.width, d.minWidth)))
const maxWidth = _.sum(column.columns.map(d => _.getFirstDefined(d.width, d.maxWidth)))
const flex = _.sum(column.columns.map(d => {
const resized = resizing.find(x => x.id === d.id) || {}
return d.width || resized.value ? 0 : d.minWidth
}))
const width = _.sum(column.columns.map(d => {
const resized = resizing.find(x => x.id === d.id) || {}
return _.getFirstDefined(resized.value, d.width, d.minWidth)
}))
const maxWidth = _.sum(column.columns.map(d => {
const resized = resizing.find(x => x.id === d.id) || {}
return _.getFirstDefined(resized.value, d.width, d.maxWidth)
}))
const theadGroupThProps = _.splitProps(getTheadGroupThProps(finalState, undefined, column, this))
const columnHeaderProps = _.splitProps(column.getHeaderProps(finalState, undefined, column, this))
@@ -491,10 +277,11 @@ export default React.createClass({
}
const makeHeader = (column, i) => {
const resized = resizing.find(x => x.id === column.id) || {}
const sort = sorting.find(d => d.id === column.id)
const show = typeof column.show === 'function' ? column.show() : column.show
const width = _.getFirstDefined(column.width, column.minWidth)
const maxWidth = _.getFirstDefined(column.width, column.maxWidth)
const width = _.getFirstDefined(resized.value, column.width, column.minWidth)
const maxWidth = _.getFirstDefined(resized.value, column.width, column.maxWidth)
const theadThProps = _.splitProps(getTheadThProps(finalState, undefined, column, this))
const columnHeaderProps = _.splitProps(column.getHeaderProps(finalState, undefined, column, this))
@@ -515,6 +302,14 @@ export default React.createClass({
...columnHeaderProps.rest
}
const resizer = resizable ? (
<ResizerComponent
onMouseDown={e => this.resizeColumnStart(column, e, false)}
onTouchStart={e => this.resizeColumnStart(column, e, true)}
{...resizerProps}
/>
) : null
if (column.expander) {
if (column.pivotColumns) {
const pivotSort = sorting.find(d => d.id === column.id)
@@ -523,9 +318,10 @@ export default React.createClass({
key={i}
className={classnames(
'rt-pivot-header',
'rt-resizable-header',
column.sortable && '-cursor-pointer',
classes,
pivotSort ? (pivotSort.desc ? '-sort-asc' : '-sort-desc') : ''
pivotSort ? (pivotSort.desc ? '-sort-desc' : '-sort-asc') : ''
)}
style={{
...styles,
@@ -538,19 +334,22 @@ export default React.createClass({
}}
{...rest}
>
{column.pivotColumns.map((pivotColumn, i) => {
return (
<span key={pivotColumn.id}>
{_.normalizeComponent(pivotColumn.header, {
data: sortedData,
column: column
})}
{i < column.pivotColumns.length - 1 && (
<ExpanderComponent />
)}
</span>
)
})}
<div className='rt-resizable-header-content'>
{column.pivotColumns.map((pivotColumn, i) => {
return (
<span key={pivotColumn.id}>
{_.normalizeComponent(pivotColumn.header, {
data: sortedData,
column: column
})}
{i < column.pivotColumns.length - 1 && (
<ExpanderComponent />
)}
</span>
)
})}
</div>
{resizer}
</ThComponent>
)
}
@@ -576,7 +375,8 @@ export default React.createClass({
key={i}
className={classnames(
classes,
sort ? (sort.desc ? '-sort-asc' : '-sort-desc') : '',
'rt-resizable-header',
sort ? (sort.desc ? '-sort-desc' : '-sort-asc') : '',
column.sortable && '-cursor-pointer',
!show && '-hidden',
)}
@@ -591,10 +391,153 @@ export default React.createClass({
}}
{...rest}
>
{_.normalizeComponent(column.header, {
data: sortedData,
column: column
})}
<div className='rt-resizable-header-content'>
{_.normalizeComponent(column.header, {
data: sortedData,
column: column
})}
</div>
{resizer}
</ThComponent>
)
}
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 resized = resizing.find(x => x.id === column.id) || {}
const width = _.getFirstDefined(resized.value, column.width, column.minWidth)
const maxWidth = _.getFirstDefined(resized.value, 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 === column.id && filter.pivotId === col.id)
pivotCols.push(
<span key={col.id}
style={{flex: 1}}>
{!col.hideFilter ? (
_.normalizeComponent(col.filterRender,
{
col,
filter,
onFilterChange: (value) => (this.filterColumn(column, value, col))
},
defaults.column.filterRender
)
) : null}
</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}
>
{!column.hideFilter ? (
_.normalizeComponent(column.filterRender,
{
column,
filter,
onFilterChange: (value) => (this.filterColumn(column, value))
},
defaults.column.filterRender
)
) : null}
</ThComponent>
)
}
@@ -627,9 +570,10 @@ export default React.createClass({
{...trProps.rest}
>
{allVisibleColumns.map((column, i2) => {
const resized = resizing.find(x => x.id === column.id) || {}
const show = typeof column.show === 'function' ? column.show() : column.show
const width = _.getFirstDefined(column.width, column.minWidth)
const maxWidth = _.getFirstDefined(column.width, column.maxWidth)
const width = _.getFirstDefined(resized.value, column.width, column.minWidth)
const maxWidth = _.getFirstDefined(resized.value, column.width, column.maxWidth)
const tdProps = _.splitProps(getTdProps(finalState, rowInfo, column, this))
const columnProps = _.splitProps(column.getProps(finalState, rowInfo, column, this))
@@ -765,7 +709,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 thProps = _.splitProps(getThProps(finalState, undefined, undefined, this))
return (
<TrGroupComponent
key={i}
@@ -778,24 +721,11 @@ export default React.createClass({
)}
style={trProps.style || {}}
>
{SubComponent && (
<ThComponent
className={classnames(
'rt-expander-header',
thProps.className
)}
style={{
...thProps.style,
flex: `0 0 auto`,
width: `${expanderColumnWidth}px`
}}
{...thProps.rest}
/>
)}
{allVisibleColumns.map((column, i2) => {
const resized = resizing.find(x => x.id === column.id) || {}
const show = typeof column.show === 'function' ? column.show() : column.show
const width = _.getFirstDefined(column.width, column.minWidth)
const maxWidth = _.getFirstDefined(column.width, column.maxWidth)
const width = _.getFirstDefined(resized.value, column.width, column.minWidth)
const maxWidth = _.getFirstDefined(resized.value, column.width, column.maxWidth)
const tdProps = _.splitProps(getTdProps(finalState, undefined, column, this))
const columnProps = _.splitProps(column.getProps(finalState, undefined, column, this))
@@ -835,79 +765,190 @@ export default React.createClass({
)
}
const makeTable = () => {
const rootProps = _.splitProps(getProps(finalState, undefined, undefined, this))
const tableProps = _.splitProps(getTableProps(finalState, undefined, undefined, this))
const tBodyProps = _.splitProps(getTbodyProps(finalState, undefined, undefined, this))
const paginationProps = _.splitProps(getPaginationProps(finalState, undefined, undefined, this))
const loadingProps = getLoadingProps(finalState, undefined, undefined, this)
const noDataProps = getNoDataProps(finalState, undefined, undefined, this)
const makeColumnFooters = () => {
const tFootProps = getTfootProps(finalState, undefined, undefined, this)
const tFootTrProps = _.splitProps(getTfootTrProps(finalState, undefined, undefined, this))
return (
<div
className={classnames(
'ReactTable',
className,
rootProps.className
)}
<TfootComponent
className={tFootProps.className}
style={{
...style,
...rootProps.style
...tFootProps.style,
minWidth: `${rowMinWidth}px`
}}
{...rootProps.rest}
{...tFootProps.rest}
>
<TableComponent
className={classnames(tableProps.className)}
style={tableProps.style}
{...tableProps.rest}
<TrComponent
className={classnames(
tFootTrProps.className
)}
style={tFootTrProps.style}
{...tFootTrProps.rest}
>
{hasHeaderGroups && makeHeaderGroups()}
{makeHeaders()}
<TbodyComponent
className={classnames(tBodyProps.className)}
style={{
...tBodyProps.style,
minWidth: `${rowMinWidth}px`
}}
{...tBodyProps.rest}
>
{pageRows.map((d, i) => makePageRow(d, i))}
{padRows.map(makePadRow)}
</TbodyComponent>
</TableComponent>
{showPagination && (
<PaginationComponent
{...resolvedState}
pages={pages}
canPrevious={canPrevious}
canNext={canNext}
onPageChange={this.onPageChange}
onPageSizeChange={this.onPageSizeChange}
className={paginationProps.className}
style={paginationProps.style}
{...paginationProps.rest}
/>
)}
{!pageRows.length && (
<NoDataComponent
{...noDataProps}
>
{_.normalizeComponent(noDataText)}
</NoDataComponent>
)}
<LoadingComponent
loading={loading}
loadingText={loadingText}
{...loadingProps}
/>
</div>
{allVisibleColumns.map((column, i2) => {
const resized = resizing.find(x => x.id === column.id) || {}
const show = typeof column.show === 'function' ? column.show() : column.show
const width = _.getFirstDefined(resized.value, column.width, column.minWidth)
const maxWidth = _.getFirstDefined(resized.value, column.width, column.maxWidth)
const tFootTdProps = _.splitProps(getTfootTdProps(finalState, undefined, undefined, this))
const columnProps = _.splitProps(column.getProps(finalState, undefined, column, this))
const columnFooterProps = _.splitProps(column.getFooterProps(finalState, undefined, column, this))
const classes = [
tFootTdProps.className,
column.className,
columnProps.className,
columnFooterProps.className
]
const styles = {
...tFootTdProps.style,
...column.style,
...columnProps.style,
...columnFooterProps.style
}
if (column.expander) {
if (column.pivotColumns) {
return (
<TdComponent
key={i2}
className={classnames(
'rt-pivot',
classes
)}
style={{
...styles,
flex: `${width} 0 auto`,
width: `${width}px`,
maxWidth: `${maxWidth}px`
}}
{...columnProps.rest}
{...tFootTdProps.rest}
{...columnFooterProps.rest}
>
{_.normalizeComponent(column.footer)}
</TdComponent>
)
}
// Return the regular expander cell
return (
<TdComponent
key={i2}
className={classnames(
classes,
{hidden: !show}
)}
style={{
...styles,
flex: `0 0 auto`,
width: `${expanderColumnWidth}px`
}}
/>
)
}
// Return regular cell
return (
<TdComponent
key={i2}
className={classnames(
classes,
!show && 'hidden'
)}
style={{
...styles,
flex: `${width} 0 auto`,
width: `${width}px`,
maxWidth: `${maxWidth}px`
}}
{...columnProps.rest}
{...tFootTdProps.rest}
{...columnFooterProps.rest}
>
{_.normalizeComponent(column.footer, {
data: sortedData,
column: column
})}
</TdComponent>
)
})}
</TrComponent>
</TfootComponent>
)
}
const rootProps = _.splitProps(getProps(finalState, undefined, undefined, this))
const tableProps = _.splitProps(getTableProps(finalState, undefined, undefined, this))
const tBodyProps = _.splitProps(getTbodyProps(finalState, undefined, undefined, this))
const paginationProps = _.splitProps(getPaginationProps(finalState, undefined, undefined, this))
const loadingProps = getLoadingProps(finalState, undefined, undefined, this)
const noDataProps = getNoDataProps(finalState, undefined, undefined, this)
const resizerProps = getResizerProps(finalState, undefined, undefined, this)
const makeTable = () => (
<div
className={classnames(
'ReactTable',
className,
rootProps.className
)}
style={{
...style,
...rootProps.style
}}
{...rootProps.rest}
>
<TableComponent
className={classnames(tableProps.className)}
style={tableProps.style}
{...tableProps.rest}
>
{hasHeaderGroups ? makeHeaderGroups() : null}
{makeHeaders()}
{showFilters ? makeFilters() : null}
<TbodyComponent
className={classnames(tBodyProps.className)}
style={{
...tBodyProps.style,
minWidth: `${rowMinWidth}px`
}}
{...tBodyProps.rest}
>
{pageRows.map((d, i) => makePageRow(d, i))}
{padRows.map(makePadRow)}
</TbodyComponent>
{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}
{!pageRows.length && (
<NoDataComponent
{...noDataProps}
>
{_.normalizeComponent(noDataText)}
</NoDataComponent>
)}
<LoadingComponent
loading={loading}
loadingText={loadingText}
{...loadingProps}
/>
</div>
)
// childProps are optionally passed to a function-as-a-child
return children ? children(finalState, makeTable, this) : makeTable()
},
// Helpers
...componentMethods
}
})
+49 -2
View File
@@ -18,10 +18,21 @@ $expandSize = 7px
.rt-thead
display: flex
flex-direction: column
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
&.-headerGroups
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)
@@ -30,7 +41,9 @@ $expandSize = 7px
.rt-th
.rt-td
color: black
padding: 5px 5px
line-height: normal
position: relative
border-right: 1px solid alpha(black, .05)
transition box-shadow .3s $easeOutBack
box-shadow:inset 0 0 0 0 transparent
@@ -42,6 +55,16 @@ $expandSize = 7px
cursor: pointer
&:last-child
border-right: 0
.rt-resizable-header
overflow: visible
&:last-child
overflow: hidden
.rt-resizable-header-content
overflow: hidden
text-overflow: ellipsis
.rt-tbody
display: flex
flex-direction: column
@@ -101,6 +124,26 @@ $expandSize = 7px
&.-open:after
transform: translate(-50%, -50%) rotate(0deg)
.rt-resizer
display: inline-block
position: absolute
width: 36px
top: 0
bottom: 0
right: -18px
cursor: col-resize
z-index: 10
.rt-tfoot
display: flex
flex-direction: column
box-shadow: 0 0px 15px 0px alpha(black, .15)
.rt-td
border-right:1px solid alpha(black, .05)
&:last-child
border-right:0
&.-striped
.rt-tr.-odd
background: alpha(black, .03)
@@ -117,6 +160,7 @@ $expandSize = 7px
flex-wrap: wrap
padding: 3px
box-shadow: 0 0px 15px 0px alpha(black, .1)
border-top: 2px solid alpha(black, .1)
.-btn
appearance:none
@@ -216,7 +260,6 @@ $expandSize = 7px
input
select
appearance: none
border: 1px solid rgba(0,0,0,0.1)
background: white
padding: 5px 7px
@@ -225,6 +268,10 @@ $expandSize = 7px
font-weight: normal
outline:none
input:not([type="checkbox"]):not([type="radio"])
select
appearance: none
.select-wrap
position:relative
display:inline-block
+116
View File
@@ -0,0 +1,116 @@
import _ from './utils'
import defaultProps from './defaultProps'
export default {
getDefaultProps () {
return defaultProps
},
getInitialState () {
return {
page: 0,
pageSize: this.props.defaultPageSize || 10,
sorting: this.props.defaultSorting,
expandedRows: {},
filtering: this.props.defaultFiltering,
resizing: this.props.defaultResizing,
currentlyResizing: undefined,
skipNextSort: false
}
},
getResolvedState (props, state) {
const resolvedState = {
..._.compactObject(this.state),
..._.compactObject(this.props),
..._.compactObject(state),
..._.compactObject(props)
}
return resolvedState
},
componentWillMount () {
this.setStateWithData(this.getDataModel(this.getResolvedState()))
},
componentDidMount () {
this.fireOnChange()
},
componentWillReceiveProps (nextProps, nextState) {
const oldState = this.getResolvedState()
const newState = this.getResolvedState(nextProps, nextState)
if (oldState.defaultSorting !== newState.defaultSorting) {
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.showFilters !== newState.showFilters ||
oldState.filtering !== newState.filtering
) {
this.setStateWithData(this.getDataModel(newState))
}
},
setStateWithData (newState, cb) {
const oldState = this.getResolvedState()
const newResolvedState = this.getResolvedState({}, newState)
const {freezeWhenExpanded} = newResolvedState
// Default to unfrozen state
newResolvedState.frozen = false
// If freezeWhenExpanded is set, check for frozen conditions
if (freezeWhenExpanded) {
// if any rows are expanded, freeze the existing data and sorting
const keys = Object.keys(newResolvedState.expandedRows)
for (var i = 0; i < keys.length; i++) {
if (newResolvedState.expandedRows[keys[i]]) {
newResolvedState.frozen = true
break
}
}
}
// If the data isn't frozen and either the data or
// sorting model has changed, update the data
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 = {}
}
Object.assign(newResolvedState, this.getSortedData(newResolvedState))
}
// Calculate pageSize all the time
if (newResolvedState.sortedData) {
newResolvedState.pages = newResolvedState.manual ? newResolvedState.pages : Math.ceil(newResolvedState.sortedData.length / newResolvedState.pageSize)
newResolvedState.page = Math.max(newResolvedState.page >= newResolvedState.pages ? newResolvedState.pages - 1 : newResolvedState.page, 0)
}
return this.setState(newResolvedState, cb)
}
}
+210 -34
View File
@@ -1,7 +1,7 @@
import _ from './utils'
export default {
getDataModel (nextProps, nextState) {
getDataModel (newState) {
const {
columns,
pivotBy = [],
@@ -10,11 +10,8 @@ export default {
pivotValKey,
subRowsKey,
expanderColumnWidth,
SubComponent,
page,
pages,
pageSize
} = this.getResolvedState(nextProps, nextState)
SubComponent
} = newState
// Determine Header Groups
let hasHeaderGroups = false
@@ -192,15 +189,14 @@ export default {
// Group the rows together for this level
let groupedRows = Object.entries(
_.groupBy(rows, keys[i]))
.map(([key, value]) => {
return {
[pivotIDKey]: keys[i],
[pivotValKey]: key,
[keys[i]]: key,
[subRowsKey]: value
}
}
)
.map(([key, value]) => {
return {
[pivotIDKey]: keys[i],
[pivotValKey]: key,
[keys[i]]: key,
[subRowsKey]: value
}
})
// Recurse into the subRows
groupedRows = groupedRows.map(rowGroup => {
let subRows = groupRecursively(rowGroup[subRowsKey], keys, i + 1)
@@ -215,32 +211,32 @@ export default {
resolvedData = groupRecursively(resolvedData, pivotBy)
}
const newPages = _.getFirstDefined(pages, Math.ceil(resolvedData.length / pageSize))
const newPage = page > newPages ? newPage - 1 : page
return {
...newState,
resolvedData,
pivotColumn,
allVisibleColumns,
headerGroups,
allDecoratedColumns,
hasHeaderGroups,
page: Math.max(newPage, 0)
hasHeaderGroups
}
},
getSortedData (resolvedState) {
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(this.filterData(resolvedData, showFilters, filtering, defaultFilterMethod, allVisibleColumns), sorting)
}
},
fireOnChange () {
this.props.onChange(this.getResolvedState(), this)
},
@@ -250,10 +246,56 @@ export default {
getStateOrProp (key) {
return _.getFirstDefined(this.state[key], this.props[key])
},
filterData (data, showFilters, filtering, defaultFilterMethod, allVisibleColumns) {
let filteredData = data
if (showFilters && filtering.length) {
filteredData = filtering.reduce(
(filteredSoFar, nextFilter) => {
return filteredSoFar.filter(
(row) => {
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 data
}
const sorted = _.orderBy(data, sorting.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
@@ -280,20 +322,23 @@ export default {
// User actions
onPageChange (page) {
const { onPageChange } = this.props
const {onPageChange, collapseOnPageChange} = this.props
if (onPageChange) {
return onPageChange(page)
}
this.setStateWithData({
expandedRows: {},
page
}, () => {
this.fireOnChange()
})
const newState = {page}
if (collapseOnPageChange) {
newState.expandedRows = {}
}
this.setStateWithData(
newState
, () => {
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
@@ -311,8 +356,20 @@ export default {
})
},
sortColumn (column, additive) {
const { sorting } = this.getResolvedState()
const { onSortingChange } = this.props
const {sorting, skipNextSort} = this.getResolvedState()
// we can't stop event propagation from the column resize move handlers
// attached to the document because of react's synthetic events
// so we have to prevent the sort function from actually sorting
// if we click on the column resize element within a header.
if (skipNextSort) {
this.setStateWithData({
skipNextSort: false
})
return
}
const {onSortingChange} = this.props
if (onSortingChange) {
return onSortingChange(column, additive)
}
@@ -394,5 +451,124 @@ export default {
}, () => {
this.fireOnChange()
})
},
filterColumn (column, value, pivotColumn) {
const {filtering} = this.getResolvedState()
const {onFilteringChange} = this.props
if (onFilteringChange) {
return onFilteringChange(column, value, pivotColumn)
}
// Remove old filter first if it exists
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 (value !== '') {
newFiltering.push({
id: column.id,
value: value,
pivotId: pivotColumn ? pivotColumn.id : undefined
})
}
this.setStateWithData({
filtering: newFiltering
}, () => {
this.fireOnChange()
})
},
resizeColumnStart (column, event, isTouch) {
const {onResize} = this.props
if (onResize) {
return onResize(column, event, isTouch)
}
const parentWidth = event.target.parentElement.getBoundingClientRect().width
let pageX
if (isTouch) {
pageX = event.changedTouches[0].pageX
} else {
pageX = event.pageX
}
this.setStateWithData({
currentlyResizing: {
id: column.id,
startX: pageX,
parentWidth: parentWidth
}
}, () => {
if (isTouch) {
document.addEventListener('touchmove', this.resizeColumnMoving)
document.addEventListener('touchcancel', this.resizeColumnEnd)
document.addEventListener('touchend', this.resizeColumnEnd)
} else {
document.addEventListener('mousemove', this.resizeColumnMoving)
document.addEventListener('mouseup', this.resizeColumnEnd)
document.addEventListener('mouseleave', this.resizeColumnEnd)
}
})
},
resizeColumnEnd (event) {
let isTouch = event.type === 'touchend' || event.type === 'touchcancel'
if (isTouch) {
document.removeEventListener('touchmove', this.resizeColumnMoving)
document.removeEventListener('touchcancel', this.resizeColumnEnd)
document.removeEventListener('touchend', this.resizeColumnEnd)
}
// If its a touch event clear the mouse one's as well because sometimes
// the mouseDown event gets called as well, but the mouseUp event doesn't
document.removeEventListener('mousemove', this.resizeColumnMoving)
document.removeEventListener('mouseup', this.resizeColumnEnd)
document.removeEventListener('mouseleave', this.resizeColumnEnd)
// The touch events don't propagate up to the sorting's onMouseDown event so
// no need to prevent it from happening or else the first click after a touch
// event resize will not sort the column.
if (!isTouch) {
this.setStateWithData({
skipNextSort: true
})
}
},
resizeColumnMoving (event) {
const {resizing, currentlyResizing} = this.getResolvedState()
// Delete old value
const newResizing = resizing.filter(x => x.id !== currentlyResizing.id)
let pageX
if (event.type === 'touchmove') {
pageX = event.changedTouches[0].pageX
} else if (event.type === 'mousemove') {
pageX = event.pageX
}
// Set the min size to 10 to account for margin and border or else the group headers don't line up correctly
const newWidth = Math.max(currentlyResizing.parentWidth + pageX - currentlyResizing.startX, 11)
newResizing.push({
id: currentlyResizing.id,
value: newWidth
})
this.setStateWithData({
resizing: newResizing
})
}
}
+14 -6
View File
@@ -4,7 +4,7 @@ import classnames from 'classnames'
// import _ from './utils'
const defaultButton = (props) => (
<button {...props} className='-btn'>{props.children}</button>
<button type='button' {...props} className='-btn'>{props.children}</button>
)
export default React.createClass({
@@ -17,12 +17,17 @@ export default React.createClass({
this.setState({page: nextProps.page})
},
getSafePage (page) {
if (isNaN(page)) {
page = this.props.page
}
return Math.min(Math.max(page, 0), this.props.pages - 1)
},
changePage (page) {
page = this.getSafePage(page)
this.setState({page})
this.props.onPageChange(page)
if (this.props.page !== page) {
this.props.onPageChange(page)
}
},
applyPage (e) {
e && e.preventDefault()
@@ -66,9 +71,7 @@ export default React.createClass({
<div className='-center'>
<span className='-pageInfo'>
{this.props.pageText} {showPageJump ? (
<form className='-pageJump'
onSubmit={this.applyPage}
>
<div className='-pageJump'>
<input
type={this.state.page === '' ? 'text' : 'number'}
onChange={e => {
@@ -81,8 +84,13 @@ export default React.createClass({
}}
value={this.state.page === '' ? '' : this.state.page + 1}
onBlur={this.applyPage}
onKeyPress={e => {
if (e.which === 13 || e.keyCode === 13) {
this.applyPage()
}
}}
/>
</form>
</div>
) : (
<span className='-currentPage'>{page + 1}</span>
)} {this.props.ofText} <span className='-totalPages'>{pages}</span>
+3 -3
View File
@@ -80,7 +80,7 @@ function orderBy (arr, funcs, dirs) {
}
return dirs[0]
? a.__index - b.__index
: b.__index - b.__index
: b.__index - a.__index
})
}
@@ -191,9 +191,9 @@ function isSortingDesc (d) {
return !!(d.sort === 'desc' || d.desc === true || d.asc === false)
}
function normalizeComponent (Comp, params, fallback = Comp) {
function normalizeComponent (Comp, params = {}, fallback = Comp) {
return typeof Comp === 'function' ? (
Comp.prototype.isReactComponent ? (
Object.getPrototypeOf(Comp).isReactComponent ? (
<Comp
{...params}
/>
+1
View File
@@ -161,6 +161,7 @@ const columns = [{
return (
<ReactTable
className='-striped -highlight'
data={data}
columns={columns}
defaultPageSize={10}
+153
View File
@@ -0,0 +1,153 @@
import React from 'react'
import _ from 'lodash'
import namor from 'namor'
import CodeHighlight from './components/codeHighlight'
import ReactTable from '../src/index'
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)
}
})
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
class ControlledTable extends React.Component {
constructor() {
super()
this.sortChange = this.sortChange.bind(this)
this.state = {
sorting: [],
page: 0,
pageSize: 10
}
}
sortChange(column, shift) {
if(shift)
alert('Shift click not implemented in this demo')
var sort = {id: column.id}
if(this.state.sorting.length && this.state.sorting[0].id == column.id)
this.state.sorting[0].asc ? sort.desc = true : sort.asc = true
else
sort.asc = true
this.setState({
sorting: [sort]
})
}
render() {
return (
<div>
<div className='table-wrap'>
<ReactTable
className='-striped -highlight'
data={data}
columns={columns}
sorting={this.state.sorting}
onSortingChange={this.sortChange}
page={this.state.page}
onPageChange={page => this.setState({page})}
pageSize={this.state.pageSize}
onPageSizeChange={(pageSize, page) => this.setState({page, pageSize})}
/>
</div>
<div style={{textAlign: 'center'}}>
<br />
<em>Tip: For simplicity, multi-sort is not implemented in this demo</em>
</div>
<CodeHighlight>{() => getCode()}</CodeHighlight>
</div>
)
}
}
function getCode () {
return `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)
}
})
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
class ControlledTable extends React.Component {
constructor() {
super()
this.sortChange = this.sortChange.bind(this)
this.state = {
sorting: [],
page: 0,
pageSize: 10
}
}
sortChange(column, shift) {
if(shift)
alert('Shift click not implemented in this demo')
var sort = {id: column.id}
if(this.state.sorting.length && this.state.sorting[0].id == column.id)
this.state.sorting[0].asc ? sort.desc = true : sort.asc = true
else
sort.asc = true
this.setState({
sorting: [sort]
})
}
render() {
return (
<ReactTable
className='-striped -highlight'
data={data}
columns={columns}
sorting={this.state.sorting}
onSortingChange={this.sortChange}
page={this.state.page}
onPageChange={page => this.setState({page})}
pageSize={this.state.pageSize}
onPageSizeChange={(pageSize, page) => this.setState({page, pageSize})}
/>
)
}
}`
}
export default () => <ControlledTable />
+264
View File
@@ -0,0 +1,264 @@
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'
}, {
header: 'Over 21',
accessor: 'age',
id: 'over',
render: ({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
},
filterRender: ({filter, onFilterChange}) => (
<select
onChange={event => onFilterChange(event.target.value)}
style={{width: '100%'}}
value={filter ? filter.value : 'all'}>
<option value="all"></option>
<option value="true">Can Drink</option>
<option value="false">Can't Drink</option>
</select>
)
}
]
}]
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) => (String(row[filter.id]) === 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 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>
<p>To completely override the filter that is shown, you can set the <strong>filterRender</strong> column
option. Using this option you can specify the JSX that is shown. The option is passed
an <strong>onFilterChange</strong> method that must be called with the value that you wan't to
pass to the <strong>filterMethod</strong> option whenever the filter has changed.</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'
}, {
header: 'Over 21',
accessor: 'age',
id: 'over',
render: ({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
},
filterRender: ({filter, onFilterChange}) => (
<select
onChange={event => onFilterChange(event.target.value)}
style={{width: '100%'}}
value={filter ? filter.value : 'all'}>
<option value="all"></option>
<option value="true">Can Drink</option>
<option value="false">Can't Drink</option>
</select>
)
}]
}]
export default (
<ReactTable
data={data}
columns={columns}
defaultPageSize={10}
defaultFilterMethod={(filter, row) => (String(row[filter.id]) === 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 />
+143
View File
@@ -0,0 +1,143 @@
import React from 'react'
import _ from 'lodash'
import namor from 'namor'
import CodeHighlight from './components/codeHighlight'
import ReactTable from '../src/index'
export default () => {
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)
}
})
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName',
footer: (
<span><strong>Popular:</strong> {
_.first(
_.reduce(
_.map(
_.groupBy(
data, d => d.firstName
)
),
(a, b) => a.length > b.length ? a : b
)
).firstName}
</span>
)
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName,
footer: (
<span><strong>Longest:</strong> {
_.reduce(
_.map(
_.groupBy(
data, d => d.lastName
),
(d, key) => key
),
(a, b) => a.length > b.length ? a : b
)}
</span>
)
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age',
footer: <span><strong>Average:</strong> {_.round(_.mean(_.map(data, d => d.age)))}</span>
}]
}]
return (
<div>
<div className='table-wrap'>
<ReactTable
className='-striped -highlight'
data={data}
columns={columns}
defaultPageSize={10}
/>
</div>
<div style={{textAlign: 'center'}}>
<br />
<em>Tip: Hold shift when sorting to multi-sort!</em>
</div>
<CodeHighlight>{() => getCode()}</CodeHighlight>
</div>
)
}
function getCode () {
return `
import ReactTable from 'react-table'
// Create some column definitions
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName',
footer: (
<span><strong>Popular:</strong> {
_.first(
_.reduce(
_.map(
_.groupBy(
data, d => d.firstName
)
),
(a, b) => a.length > b.length ? a : b
)
).firstName}
</span>
)
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName,
footer: (
<span><strong>Longest:</strong> {
_.reduce(
_.map(
_.groupBy(
data, d => d.lastName
),
(d, key) => key
),
(a, b) => a.length > b.length ? a : b
)}
</span>
)
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age',
footer: <span><strong>Average:</strong> {_.round(_.mean(_.map(data, d => d.age)))}</span>
}]
}]
// Display your table!
return (
<ReactTable
className='-striped -highlight'
data={data}
columns={columns}
defaultPageSize={10}
/>
)
`
}
+76 -43
View File
@@ -40,22 +40,89 @@ export default () => {
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
accessor: 'firstName',
footer: 'Footer'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
accessor: d => d.lastName,
footer: 'Footer'
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
accessor: 'age',
footer: 'Footer'
}]
}]
return (
<div>
<strong>Functional rendering</strong> simply means that you have all of the building blocks to render your own React Table however you'd like.
<br />
<br />
<br />
<br />
<strong>Decorating the standard table output</strong>
<br />
<br />
<div className='table-wrap'>
<ReactTable
data={data}
columns={columns}
>
{(state, makeTable, instance) => {
return (
<div style={{
background: '#ffcf00',
borderRadius: '5px',
overflow: 'hidden',
padding: '5px'
}}>
<pre><code>state.allVisibleColumns === {JSON.stringify(state.allVisibleColumns, null, 4)}</code></pre>
{makeTable()}
</div>
)
}}
</ReactTable>
</div>
<CodeHighlight>{() => `
import ReactTable from 'react-table'
return (
<ReactTable
data={data}
columns={columns}
>
{(state, Table, instance) => {
return (
<div style={{
background: '#ffcf00',
borderRadius: '5px',
overflow: 'hidden',
padding: '5px'
}}>
<pre><code>state.allVisibleColumns === {JSON.stringify(state.allVisibleColumns, null, 4)}</code></pre>
<Table />
</div>
)
}}
</ReactTable>
)
`}</CodeHighlight>
<br />
<br />
<strong>Need more control? This is the entire table state and component instance at your disposal!</strong>
<br />
<br />
<div className='table-wrap'>
<ReactTable
className='-striped -highlight'
@@ -63,54 +130,23 @@ export default () => {
columns={columns}
defaultPageSize={10}
>
{(state, makeTable, instance) => {
console.log(state)
{(state, StandardTable, instance) => {
return (
<div>
Look! This is the entire table state and component instance at your disposal!
<JSONTree
data={Object.assign({}, state, {children: 'function () {...}'})}
theme={JSONtheme}
invertTheme
/>
<br />
<br />
{makeTable()}
</div>
)
}}
</ReactTable>
</div>
<br />
<CodeHighlight>{() => getCode()}</CodeHighlight>
</div>
)
}
function getCode () {
return `
<CodeHighlight>{() => `
import ReactTable from 'react-table'
// Create some column definitions
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
// Display your table!
return (
<ReactTable
className='-striped -highlight'
@@ -118,23 +154,20 @@ return (
columns={columns}
defaultPageSize={10}
>
{(state, makeTable, instance) => {
console.log(state)
{(state, StandardTable, instance) => {
return (
<div>
Look! This is the entire table state and component instance at your disposal!
<JSONTree
data={Object.assign({}, state, {children: 'function () {...}'})}
theme={JSONtheme}
invertTheme
/>
<br />
<br />
{makeTable()}
</div>
)
}}
</ReactTable>
)
`
`}</CodeHighlight>
</div>
)
}
+10 -4
View File
@@ -33,11 +33,13 @@ 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',
aggregate: vals => _.sum(vals)
aggregate: vals => _.sum(vals),
hideFilter: true
}]
}]
@@ -50,6 +52,7 @@ export default () => {
defaultPageSize={10}
className='-striped -highlight'
pivotBy={['firstName', 'lastName']}
showFilters={true}
SubComponent={(row) => {
return (
<div style={{padding: '20px'}}>
@@ -104,11 +107,13 @@ 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',
aggregate: vals => _.sum(vals)
aggregate: vals => _.sum(vals),
hideFilter: true
}]
}]
@@ -119,6 +124,7 @@ return (
defaultPageSize={10}
className='-striped -highlight'
pivotBy={['firstName', 'lastName']}
showFilters={true}
SubComponent={(row) => {
return (
<div style={{padding: '20px'}}>
+21 -7
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.
@@ -48,13 +59,13 @@ const ServerSide = React.createClass({
}
},
fetchData (state, instance) {
console.log(state, instance)
// Whenever the table model changes, or the user sorts or changes pages, this method gets called and passed the current table model.
// 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)
this.setState({
data: res.rows,
@@ -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
+1
View File
@@ -77,6 +77,7 @@ const columns = [{
// Display your table!
return (
<ReactTable
className='-striped -highlight'
data={data}
columns={columns}
defaultPageSize={10}
+131 -65
View File
@@ -5,77 +5,139 @@ import namor from 'namor'
import CodeHighlight from './components/codeHighlight'
import ReactTable from '../src/index'
export default () => {
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)
class SubComponents 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: false
},
data: data
}
})
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
this.setTableOption = this.setTableOption.bind(this);
}
render() {
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
}]
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
return (
<div>
<div className='table-wrap'>
<ReactTable
className='-striped -highlight'
data={data}
columns={columns}
defaultPageSize={10}
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>
)
}}
/>
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}
{...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>
<CodeHighlight>{() => this.getCode()}</CodeHighlight>
</div>
<div style={{textAlign: 'center'}}>
<br />
<em>Tip: Hold shift when sorting to multi-sort!</em>
</div>
<CodeHighlight>{() => getCode()}</CodeHighlight>
</div>
)
}
)
}
function getCode () {
return `
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: [{
@@ -99,6 +161,7 @@ export default (
data={data}
columns={columns}
defaultPageSize={10}
{...otherOptions}
SubComponent={(row) => {
return (
<div style={{padding: '20px'}}>
@@ -125,4 +188,7 @@ export default (
/>
)
`
}
}
}
export default () => <SubComponents/>
+20
View File
@@ -0,0 +1,20 @@
const webpack = require('webpack')
module.exports = {
entry: './lib/index.js',
output: {
filename: './react-table.js',
libraryTarget: 'umd',
library: 'ReactTable'
},
externals: {
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react'
}
},
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
}
+161 -16
View File
@@ -121,6 +121,12 @@ accepts@~1.3.3:
mime-types "~2.1.11"
negotiator "0.6.1"
acorn-dynamic-import@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4"
dependencies:
acorn "^4.0.3"
acorn-globals@^1.0.4:
version "1.0.9"
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-1.0.9.tgz#55bb5e98691507b74579d0513413217c380c54cf"
@@ -145,10 +151,14 @@ acorn@^3.0.0, acorn@^3.0.4, acorn@^3.1.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
acorn@^4.0.1:
acorn@^4.0.1, acorn@^4.0.3:
version "4.0.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a"
acorn@^5.0.0:
version "5.0.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d"
airbnb-js-shims@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-1.0.1.tgz#7d5a7d772c8c6fdeb624ea3cef62506091b180b5"
@@ -162,7 +172,7 @@ airbnb-js-shims@^1.0.1:
string.prototype.padend "^3.0.0"
string.prototype.padstart "^3.0.0"
ajv-keywords@^1.0.0:
ajv-keywords@^1.0.0, ajv-keywords@^1.1.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
@@ -344,6 +354,12 @@ async@^1.3.0, async@^1.4.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.1.2:
version "2.3.0"
resolved "https://registry.yarnpkg.com/async/-/async-2.3.0.tgz#1013d1051047dd320fe24e494d5c66ecaf6147d9"
dependencies:
lodash "^4.14.0"
async@~0.2.6:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
@@ -1445,7 +1461,7 @@ buffer-xor@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
buffer@^4.1.0, buffer@^4.9.0:
buffer@^4.1.0, buffer@^4.3.0, buffer@^4.9.0:
version "4.9.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
dependencies:
@@ -1550,7 +1566,7 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chokidar@^1.0.0, chokidar@^1.5.1, chokidar@~1.6.0:
chokidar@^1.0.0, chokidar@^1.4.3, chokidar@^1.5.1, chokidar@~1.6.0:
version "1.6.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2"
dependencies:
@@ -1845,7 +1861,7 @@ crypto-browserify@3.3.0:
ripemd160 "0.2.0"
sha.js "2.2.6"
crypto-browserify@^3.0.0:
crypto-browserify@^3.0.0, crypto-browserify@^3.11.0:
version "3.11.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522"
dependencies:
@@ -2175,6 +2191,15 @@ encoding@^0.1.11:
dependencies:
iconv-lite "~0.4.13"
enhanced-resolve@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz#9f4b626f577245edcf4b2ad83d86e17f4f421dec"
dependencies:
graceful-fs "^4.1.2"
memory-fs "^0.4.0"
object-assign "^4.0.1"
tapable "^0.2.5"
enhanced-resolve@~0.9.0:
version "0.9.1"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e"
@@ -2998,7 +3023,7 @@ indexof@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
inflight@^1.0.4, inflight@^1.0.6:
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
dependencies:
@@ -3355,7 +3380,7 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
json5@^0.5.0:
json5@^0.5.0, json5@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
@@ -3437,6 +3462,10 @@ load-json-file@^1.0.0:
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
loader-runner@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.15, loader-utils@^0.2.16, loader-utils@^0.2.7, loader-utils@~0.2.2, loader-utils@~0.2.5:
version "0.2.16"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d"
@@ -3514,7 +3543,7 @@ lodash.uniq@^4.3.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
lodash@4.x.x, lodash@^4.0.0, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
lodash@4.x.x, lodash@^4.0.0, lodash@^4.14.0, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -3598,16 +3627,16 @@ memory-fs@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290"
memory-fs@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.3.0.tgz#7bcc6b629e3a43e871d7e29aca6ae8a7f15cbb20"
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"
dependencies:
errno "^0.1.3"
readable-stream "^2.0.1"
memory-fs@~0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
memory-fs@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.3.0.tgz#7bcc6b629e3a43e871d7e29aca6ae8a7f15cbb20"
dependencies:
errno "^0.1.3"
readable-stream "^2.0.1"
@@ -3817,6 +3846,34 @@ node-libs-browser@^0.7.0:
util "^0.10.3"
vm-browserify "0.0.4"
node-libs-browser@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646"
dependencies:
assert "^1.1.1"
browserify-zlib "^0.1.4"
buffer "^4.3.0"
console-browserify "^1.1.0"
constants-browserify "^1.0.0"
crypto-browserify "^3.11.0"
domain-browser "^1.1.1"
events "^1.0.0"
https-browserify "0.0.1"
os-browserify "^0.2.0"
path-browserify "0.0.0"
process "^0.11.0"
punycode "^1.2.4"
querystring-es3 "^0.2.0"
readable-stream "^2.0.5"
stream-browserify "^2.0.1"
stream-http "^2.3.1"
string_decoder "^0.10.25"
timers-browserify "^2.0.2"
tty-browserify "0.0.0"
url "^0.11.0"
util "^0.10.3"
vm-browserify "0.0.4"
node-pre-gyp@^0.6.29:
version "0.6.32"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.32.tgz#fc452b376e7319b3d255f5f34853ef6fd8fe1fd5"
@@ -4958,7 +5015,13 @@ right-align@^0.1.1:
dependencies:
align-text "^0.1.1"
rimraf@2, rimraf@^2.2.8, rimraf@~2.5.1, rimraf@~2.5.4:
rimraf@2, rimraf@^2.2.8, rimraf@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
dependencies:
glob "^7.0.5"
rimraf@~2.5.1, rimraf@~2.5.4:
version "2.5.4"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04"
dependencies:
@@ -5154,6 +5217,10 @@ source-list-map@^0.1.4, source-list-map@~0.1.7:
version "0.1.8"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106"
source-list-map@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.1.tgz#1a33ac210ca144d1e561f906ebccab5669ff4cb4"
source-map-support@^0.4.2:
version "0.4.10"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.10.tgz#d7b19038040a14c0837a18e630a196453952b378"
@@ -5293,7 +5360,7 @@ strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
string-width@^1.0.1:
string-width@^1.0.1, string-width@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
dependencies:
@@ -5436,6 +5503,10 @@ tapable@^0.1.8, tapable@~0.1.8:
version "0.1.10"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4"
tapable@^0.2.5, tapable@~0.2.5:
version "0.2.6"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d"
tar-pack@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.3.0.tgz#30931816418f55afc4d21775afdd6720cee45dae"
@@ -5556,6 +5627,15 @@ uglify-js@2.7.x, uglify-js@2.x.x, uglify-js@~2.7.3:
uglify-to-browserify "~1.0.0"
yargs "~3.10.0"
uglify-js@^2.8.5:
version "2.8.22"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.22.tgz#d54934778a8da14903fa29a326fb24c0ab51a1a0"
dependencies:
source-map "~0.5.1"
yargs "~3.10.0"
optionalDependencies:
uglify-to-browserify "~1.0.0"
uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
@@ -5687,6 +5767,14 @@ watchpack@^0.2.1:
chokidar "^1.0.0"
graceful-fs "^4.1.2"
watchpack@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87"
dependencies:
async "^2.1.2"
chokidar "^1.4.3"
graceful-fs "^4.1.2"
webidl-conversions@^3.0.0, webidl-conversions@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
@@ -5716,6 +5804,13 @@ webpack-hot-middleware@^2.13.2:
querystring "^0.2.0"
strip-ansi "^3.0.0"
webpack-sources@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb"
dependencies:
source-list-map "^1.1.1"
source-map "~0.5.3"
webpack@^1.13.1:
version "1.14.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-1.14.0.tgz#54f1ffb92051a328a5b2057d6ae33c289462c823"
@@ -5736,6 +5831,32 @@ webpack@^1.13.1:
watchpack "^0.2.1"
webpack-core "~0.6.9"
webpack@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.4.1.tgz#15a91dbe34966d8a4b99c7d656efd92a2e5a6f6a"
dependencies:
acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"
ajv "^4.7.0"
ajv-keywords "^1.1.1"
async "^2.1.2"
enhanced-resolve "^3.0.0"
interpret "^1.0.0"
json-loader "^0.5.4"
json5 "^0.5.1"
loader-runner "^2.3.0"
loader-utils "^0.2.16"
memory-fs "~0.4.1"
mkdirp "~0.5.0"
node-libs-browser "^2.0.0"
source-map "^0.5.3"
supports-color "^3.1.0"
tapable "~0.2.5"
uglify-js "^2.8.5"
watchpack "^1.3.1"
webpack-sources "^0.2.3"
yargs "^6.0.0"
whatwg-encoding@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz#3c6c451a198ee7aec55b1ec61d0920c67801a5f4"
@@ -5851,6 +5972,12 @@ yargs-parser@^2.4.1:
camelcase "^3.0.0"
lodash.assign "^4.0.6"
yargs-parser@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"
dependencies:
camelcase "^3.0.0"
yargs@^4.7.1:
version "4.8.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0"
@@ -5870,6 +5997,24 @@ yargs@^4.7.1:
y18n "^3.2.1"
yargs-parser "^2.4.1"
yargs@^6.0.0:
version "6.6.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
dependencies:
camelcase "^3.0.0"
cliui "^3.2.0"
decamelize "^1.1.1"
get-caller-file "^1.0.1"
os-locale "^1.4.0"
read-pkg-up "^1.0.1"
require-directory "^2.1.1"
require-main-filename "^1.0.1"
set-blocking "^2.0.0"
string-width "^1.0.2"
which-module "^1.0.0"
y18n "^3.2.1"
yargs-parser "^4.2.0"
yargs@~3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"