Merge branch 'master' into linter_dependencies

This commit is contained in:
Gary Menzel
2017-11-23 13:45:25 +11:00
committed by GitHub
15 changed files with 858 additions and 14 deletions
+4
View File
@@ -171,6 +171,7 @@ These are all of the available props (and their default values) for the main `<R
collapseOnDataChange: true,
freezeWhenExpanded: false,
sortable: true,
multiSort: true,
resizable: true,
filterable: false,
defaultSortDesc: false,
@@ -833,6 +834,9 @@ Sorting comes built in with React-Table.
## 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!
You can set the `multiSort` prop to `false` to disable this feature (which may be useful for server-side sorting when you are not
going to sort multiple columns).
## Custom Sorting Algorithm
To override the default sorting algorithm for the whole table use the `defaultSortMethod` prop.
+9
View File
@@ -10,6 +10,15 @@
"babel-preset-react": "6.11.1",
"babel-preset-stage-2": "6.13.0",
"eslint": "^4.1.1",
"eslint-config-standard": "^10.2.1",
"eslint-plugin-class-property": "^1.0.6",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-react": "^7.4.0",
"eslint-plugin-standard": "^3.0.1",
"html-element-attributes": "^1.3.0",
"match-sorter": "^1.8.0",
"npm-run-all": "^3.1.1",
"onchange": "^3.0.2",
+19 -4
View File
@@ -1,3 +1,5 @@
/* eslint-disable */
import React from 'react'
//
import ReactStory, { defaultProps } from 'react-story'
@@ -6,6 +8,18 @@ import './stories/utils/prism.css'
import '../../react-table.css'
import Readme from './stories/Readme.js'
import HOCReadme from './stories/HOCReadme.js'
// import Tester from './examples/expander';
// import { TreeTable, SelectTable, SelectTreeTable } from './examples/index'
//
// const exampleStories = [
// // examples
// { name: 'TreeTable', component: TreeTable },
// { name: 'SelectTable', component: SelectTable },
// { name: 'SelectTreeTable', component: SelectTreeTable },
// ]
import { TreeTable, CheckboxTable } from './examples/index'
@@ -17,7 +31,9 @@ const exampleStories = [
const stories = [
{ name: 'Readme', component: Readme },
{ name: 'HOC Readme', component: HOCReadme },
// { name: 'Tester', component: Tester },
{ name: 'Simple Table', component: CodeSandbox('X6npLXPRW') },
{
name: 'Cell Renderers & Custom Components',
@@ -60,10 +76,9 @@ const stories = [
name: 'Multiple Pagers (Top and Bottom)',
component: CodeSandbox('VEZ8OgvX'),
},
// other examples
...exampleStories,
{ name: 'Tree Table (HOC)', component: CodeSandbox('lxmr4wynzq') },
{ name: 'Select Table (HOC)', component: CodeSandbox('7yq5ylw09j') },
{ name: 'Select Tree Table (HOC)', component: CodeSandbox('2p7jp4klwp') },
]
export default class App extends React.Component {
+54
View File
@@ -0,0 +1,54 @@
/* eslint-disable */
import React from 'react';
import ReactTable from '../../../../lib/index'
import '../../../../react-table.css'
const data = [
{one:"1.1",two:"1.2"},
{one:"2.1",two:"2.2"},
{one:"3.1",two:"3.2"},
{one:"4.1",two:"4.2"},
]
const columns = [
{accessor:'one', Header: 'One'},
{accessor:'two', Header: 'Two'},
]
class ExpanderComponent extends React.Component {
render()
{
return (
<div className={`rt-expander ${this.props.isExpanded ? '-open' : ''}`}>
&bull;
</div>
)
}
}
class SubComponent extends React.Component {
render()
{
return <div>Nothing</div>
}
}
export default class ComponentTest extends React.Component {
render()
{
const rtProps = {
data,
columns,
ExpanderComponent: (props)=><ExpanderComponent {...props} />,
SubComponent: (props)=><SubComponent {...props} />,
multiSort: false,
}
return (
<ReactTable
{...rtProps}
/>
)
}
}
+6 -3
View File
@@ -1,8 +1,11 @@
/* eslint-disable */
import TreeTable from './treetable';
import CheckboxTable from './checkbox';
import TreeTable from './treetable'
import SelectTable from './selecttable'
import SelectTreeTable from './selecttreetable'
export {
TreeTable,
CheckboxTable,
SelectTable,
SelectTreeTable,
}
+174
View File
@@ -0,0 +1,174 @@
import React from 'react';
import shortid from 'shortid';
import ReactTable from '../../../../lib/index'
import '../../../../react-table.css'
import selectTableHOC from '../../../../lib/hoc/selectTable'
const SelectTable = selectTableHOC(ReactTable);
async function getData()
{
const result = await ( await fetch('/au_500_tree.json') ).json();
// we are adding a unique ID to the data for tracking the selected records
return result.map((item)=>{
const _id = shortid.generate();
return {
_id,
...item,
}
});
}
function getColumns(data)
{
const columns = [];
const sample = data[0];
for(let key in sample)
{
if(key==='_id') continue;
columns.push({
accessor: key,
Header: key,
})
}
return columns;
}
export class ComponentTest extends React.Component {
constructor(props) {
super(props);
this.state =
{
data: null,
columns: null,
selection: [],
selectAll: false,
selectType: 'checkbox',
};
}
componentDidMount()
{
getData().then((data)=>{
const columns = getColumns(data);
this.setState({ data, columns });
});
}
toggleSelection = (key,shift,row) => {
/*
Implementation of how to manage the selection state is up to the developer.
This implementation uses an array stored in the component state.
Other implementations could use object keys, a Javascript Set, or Redux... etc.
*/
// start off with the existing state
if (this.state.selectType === 'radio') {
let selection = [];
if (selection.indexOf(key)<0) selection.push(key);
this.setState({selection});
} else {
let selection = [
...this.state.selection
];
const keyIndex = selection.indexOf(key);
// check to see if the key exists
if(keyIndex>=0) {
// it does exist so we will remove it using destructing
selection = [
...selection.slice(0,keyIndex),
...selection.slice(keyIndex+1)
]
} else {
// it does not exist so add it
selection.push(key);
}
// update the state
this.setState({selection});
}
}
toggleAll = () => {
/*
'toggleAll' is a tricky concept with any filterable table
do you just select ALL the records that are in your data?
OR
do you only select ALL the records that are in the current filtered data?
The latter makes more sense because 'selection' is a visual thing for the user.
This is especially true if you are going to implement a set of external functions
that act on the selected information (you would not want to DELETE the wrong thing!).
So, to that end, access to the internals of ReactTable are required to get what is
currently visible in the table (either on the current page or any other page).
The HOC provides a method call 'getWrappedInstance' to get a ref to the wrapped
ReactTable and then get the internal state and the 'sortedData'.
That can then be iterrated to get all the currently visible records and set
the selection state.
*/
const selectAll = this.state.selectAll?false:true;
const selection = [];
if(selectAll)
{
// we need to get at the internals of ReactTable
const wrappedInstance = this.selectTable.getWrappedInstance();
// the 'sortedData' property contains the currently accessible records based on the filter and sort
const currentRecords = wrappedInstance.getResolvedState().sortedData;
// we just push all the IDs onto the selection array
currentRecords.forEach((item)=>{
if(item._original)
{
selection.push(item._original._id);
}
})
}
this.setState({selectAll,selection})
}
isSelected = (key) => {
/*
Instead of passing our external selection state we provide an 'isSelected'
callback and detect the selection state ourselves. This allows any implementation
for selection (either an array, object keys, or even a Javascript Set object).
*/
return this.state.selection.includes(key);
}
logSelection = () => {
console.log('selection:',this.state.selection);
}
toggleType = () => {
this.setState({ selectType: this.state.selectType === 'radio' ? 'checkbox' : 'radio', selection: [], selectAll: false, });
}
render(){
const { toggleSelection, toggleAll, isSelected, logSelection, toggleType } = this;
const { data, columns, selectAll, selectType } = this.state;
const extraProps =
{
selectAll,
isSelected,
toggleAll,
toggleSelection,
selectType,
}
return (
<div style={{ padding: '10px'}}>
<h1>react-table - Select Table</h1>
<button onClick={toggleType}>Select Type: <strong>{selectType}</strong></button>
<button onClick={logSelection}>Log Selection to Console</button>
{` (${this.state.selection.length}) selected`}
{
data?
<SelectTable
data={data}
columns={columns}
ref={(r)=>this.selectTable = r}
className="-striped -highlight"
{...extraProps}
/>
:null
}
</div>
);
}
}
export default ComponentTest;
+216
View File
@@ -0,0 +1,216 @@
import React from 'react';
import shortid from 'shortid';
import ReactTable from '../../../../lib/index'
import '../../../../react-table.css'
import selectTableHOC from '../../../../lib/hoc/selectTable'
import treeTableHOC from '../../../../lib/hoc/treeTable'
const SelectTreeTable = selectTableHOC(treeTableHOC(ReactTable));
async function getData()
{
const result = await ( await fetch('/au_500_tree.json') ).json();
// we are adding a unique ID to the data for tracking the selected records
return result.map((item)=>{
const _id = shortid.generate();
return {
_id,
...item,
}
});
}
function getColumns(data)
{
const columns = [];
const sample = data[0];
for(let key in sample)
{
if(key==='_id') continue;
columns.push({
accessor: key,
Header: key,
})
}
return columns;
}
function getNodes(data,node=[])
{
data.forEach((item)=>{
if(item.hasOwnProperty('_subRows') && item._subRows)
{
node = getNodes(item._subRows,node);
} else {
node.push(item._original);
}
});
return node;
}
export class ComponentTest extends React.Component {
constructor(props) {
super(props);
this.state =
{
data: null,
columns: null,
selection: [],
selectAll: false,
selectType: 'checkbox',
};
}
componentDidMount()
{
getData().then((data)=>{
const columns = getColumns(data);
const pivotBy = ['state','post'];
this.setState({ data, columns, pivotBy });
});
}
toggleSelection = (key,shift,row) => {
/*
Implementation of how to manage the selection state is up to the developer.
This implementation uses an array stored in the component state.
Other implementations could use object keys, a Javascript Set, or Redux... etc.
*/
// start off with the existing state
if (this.state.selectType === 'radio') {
let selection = [];
if (selection.indexOf(key)<0) selection.push(key);
this.setState({selection});
} else {
let selection = [
...this.state.selection
];
const keyIndex = selection.indexOf(key);
// check to see if the key exists
if(keyIndex>=0) {
// it does exist so we will remove it using destructing
selection = [
...selection.slice(0,keyIndex),
...selection.slice(keyIndex+1)
]
} else {
// it does not exist so add it
selection.push(key);
}
// update the state
this.setState({selection});
}
}
toggleAll = () => {
/*
'toggleAll' is a tricky concept with any filterable table
do you just select ALL the records that are in your data?
OR
do you only select ALL the records that are in the current filtered data?
The latter makes more sense because 'selection' is a visual thing for the user.
This is especially true if you are going to implement a set of external functions
that act on the selected information (you would not want to DELETE the wrong thing!).
So, to that end, access to the internals of ReactTable are required to get what is
currently visible in the table (either on the current page or any other page).
The HOC provides a method call 'getWrappedInstance' to get a ref to the wrapped
ReactTable and then get the internal state and the 'sortedData'.
That can then be iterrated to get all the currently visible records and set
the selection state.
*/
const selectAll = this.state.selectAll?false:true;
const selection = [];
if(selectAll)
{
// we need to get at the internals of ReactTable
const wrappedInstance = this.selectTable.getWrappedInstance();
// the 'sortedData' property contains the currently accessible records based on the filter and sort
const currentRecords = wrappedInstance.getResolvedState().sortedData;
// we need to get all the 'real' (original) records out to get at their IDs
const nodes = getNodes(currentRecords);
// we just push all the IDs onto the selection array
nodes.forEach((item)=>{
selection.push(item._id);
})
}
this.setState({selectAll,selection})
}
isSelected = (key) => {
/*
Instead of passing our external selection state we provide an 'isSelected'
callback and detect the selection state ourselves. This allows any implementation
for selection (either an array, object keys, or even a Javascript Set object).
*/
return this.state.selection.includes(key);
}
logSelection = () => {
console.log('selection:',this.state.selection);
}
toggleType = () => {
this.setState({ selectType: this.state.selectType === 'radio' ? 'checkbox' : 'radio', selection: [], selectAll: false, });
}
toggleTree = () => {
if(this.state.pivotBy.length) {
this.setState({pivotBy:[],expanded:{}});
} else {
this.setState({pivotBy:['state','post'],expanded:{}});
}
}
onExpandedChange = (expanded) => {
this.setState({expanded});
}
render(){
const { toggleSelection, toggleAll, isSelected, logSelection, toggleType, toggleTree, onExpandedChange, } = this;
const { data, columns, selectAll, selectType, pivotBy, expanded, } = this.state;
const extraProps =
{
selectAll,
isSelected,
toggleAll,
toggleSelection,
selectType,
pivotBy,
expanded,
onExpandedChange,
pageSize: 5,
}
return (
<div style={{ padding: '10px'}}>
<h1>react-table - Select Tree Table</h1>
<p>This example combines two HOCs (the TreeTable and the SelectTable) to make a composite component.</p>
<p>We'll call it SelectTreeTable!</p>
<p>Here is what the buttons do:</p>
<ul>
<li><strong>Toggle Tree:</strong> enables or disabled the pivotBy on the table.</li>
<li><strong>Select Type:</strong> changes from 'checkbox' to 'radio' and back again.</li>
<li><strong>Log Selection to Console:</strong> open your console to see what has been selected.</li>
</ul>
<p>
<strong>NOTE:</strong> the selection is maintained when toggling the tree on and off but is cleared
when switching between select types (radio, checkbox).
</p>
<button onClick={toggleTree}>Toggle Tree [{pivotBy && pivotBy.length ? pivotBy.join(', ') : ''}]</button>
<button onClick={toggleType}>Select Type: <strong>{selectType}</strong></button>
<button onClick={logSelection}>Log Selection to Console</button>
{` (${this.state.selection.length}) selected`}
{
data?
<SelectTreeTable
data={data}
columns={columns}
ref={(r)=>this.selectTable = r}
className="-striped -highlight"
freezeWhenExpanded={true}
{...extraProps}
/>
:null
}
</div>
);
}
}
export default ComponentTest;
+1 -1
View File
@@ -4,7 +4,7 @@ import React from 'react';
import ReactTable from '../../../../lib/index'
import '../../../../react-table.css'
import treeTableHOC from './treeTableHOC';
import treeTableHOC from '../../../../lib/hoc/treeTable'
async function getData()
{
+23
View File
@@ -0,0 +1,23 @@
/* eslint-disable */
import React from 'react'
import marked from 'marked'
//
import HOCReadme from '!raw!../../../src/hoc/README.md'
import 'github-markdown-css/github-markdown.css'
import './utils/prism.js'
export default class HOCStory extends React.Component {
render () {
return (
<div style={{ padding: '10px' }}>
<span
className='markdown-body'
dangerouslySetInnerHTML={{ __html: marked(HOCReadme) }}
/>
</div>
)
}
componentDidMount () {
global.Prism && global.Prism.highlightAll()
}
}
+34 -3
View File
@@ -2417,7 +2417,7 @@ eslint-config-standard-jsx@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-4.0.2.tgz#009e53c4ddb1e9ee70b4650ffe63a7f39f8836e1"
eslint-config-standard@10.2.1:
eslint-config-standard@10.2.1, eslint-config-standard@^10.2.1:
version "10.2.1"
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz#c061e4d066f379dc17cd562c64e819b4dd454591"
@@ -2492,7 +2492,7 @@ eslint-plugin-import@2.0.1:
minimatch "^3.0.3"
pkg-up "^1.0.0"
eslint-plugin-import@^2.7.0:
eslint-plugin-import@^2.7.0, eslint-plugin-import@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz#fa1b6ef31fcb3c501c09859c1b86f1fc5b986894"
dependencies:
@@ -2545,6 +2545,15 @@ eslint-plugin-jsx-a11y@^6.0.2:
emoji-regex "^6.1.0"
jsx-ast-utils "^1.4.0"
eslint-plugin-node@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-5.2.1.tgz#80df3253c4d7901045ec87fa660a284e32bdca29"
dependencies:
ignore "^3.3.6"
minimatch "^3.0.4"
resolve "^1.3.3"
semver "5.3.0"
eslint-plugin-node@~4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-4.2.2.tgz#82959ca9aed79fcbd28bb1b188d05cac04fb3363"
@@ -2555,6 +2564,10 @@ eslint-plugin-node@~4.2.2:
resolve "^1.1.7"
semver "5.3.0"
eslint-plugin-promise@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.6.0.tgz#54b7658c8f454813dc2a870aff8152ec4969ba75"
eslint-plugin-promise@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.5.0.tgz#78fbb6ffe047201627569e85a6c5373af2a68fca"
@@ -2585,7 +2598,7 @@ eslint-plugin-react@~6.10.0:
jsx-ast-utils "^1.3.4"
object.assign "^4.0.4"
eslint-plugin-standard@~3.0.1:
eslint-plugin-standard@^3.0.1, eslint-plugin-standard@~3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz#34d0c915b45edc6f010393c7eef3823b08565cf2"
@@ -3479,6 +3492,10 @@ html-element-attributes@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/html-element-attributes/-/html-element-attributes-1.2.0.tgz#8b1c7aaf94353fd9b455c27ec7ebaf1583e29fd0"
html-element-attributes@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/html-element-attributes/-/html-element-attributes-1.3.0.tgz#f06ebdfce22de979db82020265cac541fb17d4fc"
html-encoding-sniffer@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
@@ -3612,6 +3629,10 @@ ignore@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
ignore@^3.3.6:
version "3.3.7"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -6113,6 +6134,12 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0:
dependencies:
path-parse "^1.0.5"
resolve@^1.3.3:
version "1.5.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
dependencies:
path-parse "^1.0.5"
restore-cursor@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
@@ -6322,6 +6349,10 @@ shellwords@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
shortid@^2.2.8:
version "2.2.8"
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131"
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+1
View File
@@ -22,6 +22,7 @@ export default {
collapseOnDataChange: true,
freezeWhenExpanded: false,
sortable: true,
multiSort: true,
resizable: true,
filterable: false,
defaultSortDesc: false,
+122
View File
@@ -0,0 +1,122 @@
<div style="text-align:center;">
<a href="https://github.com/react-tools/react-table" target="\_parent"><img src="https://github.com/react-tools/media/raw/master/logo-react-table.png" alt="React Table Logo" style="width:450px;"/></a>
</div>
# ReactTable - expanding with HOCs
This documentation is about expanding ReactTable using Higher Order Components/Functions.
## Covered in this README
- A Brief explanation of HOCs and why they are a good approach for ReactTable enhancements
- Documentation of the currently available HOCs
- TreeTable
- SelectTable
- Documentation of the standard for writing HOCs with ReactTable
## What are HOCs and why use them with ReactTable
HOCs (or Higher Order Components/Functions) are either a React Component (or a function that returns a React Component)
that are used to enhance the functionality of an existing component. How much you can enhance depends on the props that
the component exposes.
Fortunately, ReactTable exposes a LOT of functionality as props to the component. In some cases there are too many
props to keep track of and that is where HOCs come in.
You can write a HOC that just focusses on the additional functionality you want to enhance and keep those enhancements to
reuse over and over again when you need them. You don't have to edit the ReactSource code, just wrap ReactTable in one or
more HOCs (more on some issues related to chaining HOCs later) that provide the additional functionality you want to expose.
The most obvious HOC is one that can add `checkbox` or select functionality. The HOC included provides `select` functionality
that allows the developer to specify if they want a `checkbox` or `radio` style of select column. The implementation of the
selection is recorded (e.g. in component state, Redux, etc.) and how to manage multiple selections. The HOC really only handles
the rendering pieces.
But there is more documentation on the `select` HOC below.
## Currently Available HOCs
### TreeTable
TreeTable takes over the rendering of the generated pivot rows of ReactTable so that they appear more like an expandable Tree.
It accomplishes this by rendering a 100% wide div and then only rendering the cell that controls the pivot at that level.
Using it is as simple as doing the following:
```javascript
import ReactTable from 'react-table'
import treeTableHOC from 'react-table/lib/hoc/treeTable'
const TreeTable = treeTableHOC(ReactTable)
```
After you have done the above, you can then use `TreeTable` just as you would `ReactTable` but it will render pivots using
the Tree style described above.
### SelectTable
SelectTable is a little trickier. The HOCs attempt to avoid adding additional state and, as there is no internal ID for a row that
can be relied on to be static (ReactTable just reuses indexes when rendering) the developer has to maintain the state outside of even
the wrapped component. So it is largely based on callbacks.
You include the HOC in the same manner as you would for the treeTableHOC but then need to provide the following overrides:
- isSelected - returns `true` if the key passed is selected otherwise it should return `false`
- selectAll - a property that indicates if the selectAll is set (`true|false`)
- toggleAll - called when the user clicks the `selectAll` checkbox/radio
- toggleSelection - called when the use clicks a specific checkbox/radio in a row
- selectType - either `checkbox|radio` to indicate what type of selection is required
In the case of `radio` there is no `selectAll` displayed but the developer is responsible for only making one selection in
the controlling component's state. You could select multiple but it wouldn't make sense and you should use `checkbox` instead.
You also have to decide what `selectAll` means. Given ReactTable is a paged solution there are other records off-page. When someone
selects the `selectAll` checkbox, should it mark every possible record, only what might be visible to due a Filter or only those items
on the current page?
The example opts for the middle approach so it gets a `ref` to the ReactTable instance and pulls the `sortedData` out of the resolved
state (then walks through those records and pulls their ID into the `selection` state of the controlling component).
You can also replace the input component that is used to render the select box and select all box:
- SelectAllInputComponent - the checkbox in the top left corner
- SelectInputComponent - the checkbox used on a row
### SelectTreeTable
SelectTreeTable is a combination of TreeTable and SelectTable.
To function correctly the chain has to be in the correct order as follows (see the comments in the guid on HOCs below).
```Javascript
const SelectTreeTable = selectTableHOC(treeTableHOC(ReactTable));
```
In this particular instance it is (probably) because the functions need access to the state on the wrapped component to manage
the selected items. Although that is not totally clearly the issue.
## HOC Guide for ReactTable
There are a few rules required when writing a HOC for ReactTable (other than meeting the normal lint standards - which are
still being developed).
Firstly, there are issues with `ref` when you write a HOC. Consider a deeply nested component wrapped in multiple HOCs...
A HOC in the middle of the chain requires access to the instance of the component it thinks it is wrapping but there is at
least one other wrapper in the way. The challenge is: How do I get to the actual wrapped component?
Each HOC is required to be a React Class so that a `ref` can be obtained against each component:
```Javascript
<Component ... ref={r => this.wrappedInstance = r} />
```
*NOTE:* "Component" can also be the `<ReactTable />` instance.
Then the following method needs
to be placed on the class so that it exposes the correct instance of ReactTable:
```Javascript
getWrappedInstance() {
if (!this.wrappedInstance) console.warn('<component name here> - No wrapped instance')
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance()
else return this.wrappedInstance
}
```
Essentially this will walk down the chain (if there are chained HOCs) and stop when it gets to the end and return the wrapped instance.
Finally, sometimes the chains need to be in a specific order to function correctly. It is not clear if this is just an architectural
issue or if it would be better solved using a library like `recompose`. Anyone who is able to contribute a reliable solution to this
is welcome to submit a PR.
+111
View File
@@ -0,0 +1,111 @@
/* eslint-disable */
import React from 'react';
const defaultSelectInputComponent = (props) => {
return (
<input
type={props.selectType || 'checkbox'}
checked={props.checked}
onClick={(e)=>{
const { shiftKey } = e;
e.stopPropagation();
props.onClick(props.id, shiftKey, props.row);
}}
onChange={()=>{}}
/>
)
}
export default (Component) => {
const wrapper = class RTSelectTable extends React.Component {
constructor(props)
{
super(props);
}
rowSelector(row) {
if(!row || !row.hasOwnProperty(this.props.keyField)) return null;
const { toggleSelection, selectType, keyField } = this.props;
const checked = this.props.isSelected(row[this.props.keyField]);
const inputProps =
{
checked,
onClick: toggleSelection,
selectType,
id: row[keyField],
row,
}
return React.createElement(this.props.SelectInputComponent,inputProps);
}
headSelector(row) {
const { selectType } = this.props;
if (selectType === 'radio') return null;
const { toggleAll, selectAll: checked, SelectAllInputComponent, } = this.props;
const inputProps =
{
checked,
onClick: toggleAll,
selectType,
}
return React.createElement(SelectAllInputComponent,inputProps);
}
// this is so we can expose the underlying ReactTable to get at the sortedData for selectAll
getWrappedInstance() {
if (!this.wrappedInstance) console.warn('RTSelectTable - No wrapped instance');
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance();
else return this.wrappedInstance
}
render()
{
const {
columns:originalCols, isSelected, toggleSelection, toggleAll, keyField, selectAll,
selectType, SelectAllInputComponent, SelectInputComponent,
...rest
} = this.props;
const select = {
id: '_selector',
accessor: ()=>'x', // this value is not important
Header: this.headSelector.bind(this),
Cell: (ci) => { return this.rowSelector.bind(this)(ci.original); },
width: 30,
filterable: false,
sortable: false,
resizable: false,
style: { textAlign: 'center' },
}
const columns = [
select,
...originalCols,
];
const extra = {
columns,
};
return (
<Component {...rest} {...extra} ref={(r)=>this.wrappedInstance=r}/>
)
}
}
wrapper.displayName = 'RTSelectTable';
wrapper.defaultProps =
{
keyField: '_id',
isSelected: (key)=>{ console.log('No isSelected handler provided:',{key})},
selectAll: false,
toggleSelection: (key, shift, row)=>{ console.log('No toggleSelection handler provided:', { key, shift, row }) },
toggleAll: () => { console.log('No toggleAll handler provided.') },
selectType: 'check',
SelectInputComponent: defaultSelectInputComponent,
SelectAllInputComponent: defaultSelectInputComponent,
}
return wrapper;
}
+80
View File
@@ -0,0 +1,80 @@
/* eslint-disable */
import React from 'react'
export default (Component) => {
const wrapper = class RTTreeTable extends React.Component {
constructor(props)
{
super(props);
this.getWrappedInstance.bind(this);
this.TrComponent.bind(this);
this.getTrProps.bind(this);
}
// this is so we can expose the underlying ReactTable to get at the sortedData for selectAll
getWrappedInstance = () => {
if (!this.wrappedInstance) console.warn('RTTreeTable - No wrapped instance');
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance();
else return this.wrappedInstance
}
TrComponent = (props) => {
const {
ri,
...rest
} = props;
if(ri && ri.groupedByPivot) {
const cell = {...props.children[ri.level]};
cell.props.style.flex = 'unset';
cell.props.style.width = '100%';
cell.props.style.maxWidth = 'unset';
cell.props.style.paddingLeft = `${this.props.treeTableIndent*ri.level}px`;
// cell.props.style.backgroundColor = '#DDD';
cell.props.style.borderBottom = '1px solid rgba(128,128,128,0.2)';
return <div className={`rt-tr ${rest.className}`} style={rest.style}>{cell}</div>;
}
return <Component.defaultProps.TrComponent {...rest} />;
}
getTrProps = (state,ri,ci,instance) => {
return {ri};
}
render() {
const { columns, treeTableIndent, ...rest } = this.props;
const { TrComponent, getTrProps } = this;
const extra = {
columns: columns.map((col)=>{
let column = col;
if(rest.pivotBy && rest.pivotBy.includes(col.accessor))
{
column = {
accessor: col.accessor,
width: `${treeTableIndent}px`,
show: false,
Header: '',
}
}
return column;
}),
TrComponent,
getTrProps,
};
return (
<Component {...rest} {...extra} ref={ r => this.wrappedInstance=r }/>
)
}
}
wrapper.displayName = 'RTTreeTable';
wrapper.defaultProps =
{
treeTableIndent: 10,
}
return wrapper;
}
+4 -3
View File
@@ -80,6 +80,7 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
loadingText,
noDataText,
sortable,
multiSort,
resizable,
filterable,
// Pivoting State
@@ -365,9 +366,9 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
width: _.asPx(width),
maxWidth: _.asPx(maxWidth),
}}
toggleSort={e => (
isSortable && this.sortColumn(column, e.shiftKey)
)}
toggleSort={e => {
isSortable && this.sortColumn(column, multiSort ? e.shiftKey : false)
}}
{...rest}
>
<div className={classnames(isResizable && 'rt-resizable-header-content')}>