add FoldableTable (#937)

I have used this library to develop a big application for my company and had been required to make the columns are foldable. I saw that feature is useful and would like to share with everybody. Especially to all developers who developed this great component.

Let me know if any issues with My component. I will fix it accordingly.
This commit is contained in:
Steven 2018-05-25 06:32:34 +08:00 committed by Tanner Linsley
parent 621c3bf092
commit dd4c1e2df6
10 changed files with 621 additions and 10 deletions

View File

@ -15,8 +15,8 @@ import HOCReadme from "./stories/HOCReadme.js";
// import Tester from './examples/expander';
const stories = [
{ name: "Readme", component: Readme },
{ name: "HOC Readme", component: HOCReadme },
{ name: 'Readme', component: Readme },
{ name: 'HOC Readme', component: HOCReadme },
// { name: 'Tester', component: Test },
{ name: "Simple Table", component: CodeSandbox("X6npLXPRW") },
@ -61,10 +61,12 @@ const stories = [
name: "Multiple Pagers (Top and Bottom)",
component: CodeSandbox("VEZ8OgvX")
},
{ name: "Tree Table (HOC)", component: CodeSandbox("lxmr4wynzq") },
{ name: "Select Table (HOC)", component: CodeSandbox("7yq5ylw09j") },
{ name: "Select Tree Table (HOC)", component: CodeSandbox("2p7jp4klwp") }
];
{ name: 'Tree Table (HOC)', component: CodeSandbox('lxmr4wynzq') },
{ name: 'Select Table (HOC)', component: CodeSandbox('7yq5ylw09j') },
{ name: 'Select Tree Table (HOC)', component: CodeSandbox('2p7jp4klwp') },
{ name: 'Foldable Table (HOC)', component: CodeSandbox('2p7jp4klwp') },
]
export default class App extends React.Component {
render() {

View File

@ -0,0 +1,110 @@
import React from 'react'
import ReactTable from '../../../../lib/index'
import FoldableTableHOC from '../../../../lib/hoc/foldableTable'
import selectTableHOC from '../../../../lib/hoc/selectTable'
const FoldableTable = FoldableTableHOC(selectTableHOC(ReactTable))
export default class FoldableTableCustomState extends React.Component {
constructor(props, context) {
super(props, context)
this.state = { folded: {}, seleted: {}, selectedAll: false }
}
getData = () => [{
id: 1,
first_name: 'Jeanette',
'last_name': 'Penddreth',
email: 'jpenddreth0@census.gov',
'gender': 'Female',
ip_address: '26.58.193.2',
}, {
'id': 2,
'first_name': 'Giavani',
last_name: 'Frediani',
'email': 'gfrediani1@senate.gov',
'gender': 'Male',
'ip_address': '229.179.4.212',
}, {
'id': 3,
first_name: 'Noell',
last_name: 'Bea',
'email': 'nbea2@imageshack.us',
gender: 'Female',
ip_address: '180.66.162.255',
}, {
'id': 4,
'first_name': 'Willard',
'last_name': 'Valek',
email: 'wvalek3@vk.com',
'gender': 'Male',
ip_address: '67.76.188.26',
}];
toggleSelection = (key, shift, row) => {
const { selected } = this.state
let newSelected = Object.assign({}, selected)
newSelected[key] = !newSelected[key]
this.setState(p => ({ selected: newSelected }))
};
toggleAll = () => {
const { selectedAll } = this.state
if (selectedAll) { this.setState(p => { return { selectedAll: false, seleted: {} } }); }
else {
const data = this.getData()
let newSelected = {}
data.forEach(d => newSelected[d.id] = true)
this.setState(p => ({ selectedAll: true, seleted: newSelected }))
}
}
isSelected = key => this.state.seleted[key];
render() {
return (<FoldableTable
//SelectTable props
keyField='id'
toggleSelection={this.toggleSelection}
toggleAll={this.toggleAll}
isSelected={this.isSelected}
selectAll={this.state.selectedAll}
selectType='checkbox'
onFoldChange={newFolded => this.setState(p => { return { folded: newFolded } })}
folded={this.state.folded}
data={this.getData()}
columns={[{
Header: "Name",
foldable: true,
columns: [
{
Header: "First Name",
accessor: "first_name"
},
{
Header: "Last Name",
accessor: "last_name"
}
]
}, {
Header: "Info",
foldable: true,
columns: [
{
Header: "Email",
accessor: "email"
},
{
Header: "Gender",
accessor: "gender"
}
]
}]
}></FoldableTable>)
}
}

View File

@ -0,0 +1,71 @@
import React from 'react'
import ReactTable from '../../../../lib/index'
import FoldableTableHOC from '../../../../lib/hoc/foldableTable'
const FoldableTable = FoldableTableHOC(ReactTable)
export default class FoldableTableWithHeader extends React.Component {
getData = () => [{
id: 1,
first_name: 'Jeanette',
last_name: 'Penddreth',
'email': 'jpenddreth0@census.gov',
gender: 'Female',
'ip_address': '26.58.193.2',
}, {
'id': 2,
first_name: 'Giavani',
last_name: 'Frediani',
email: 'gfrediani1@senate.gov',
gender: 'Male',
'ip_address': '229.179.4.212',
}, {
'id': 3,
'first_name': 'Noell',
last_name: 'Bea',
'email': 'nbea2@imageshack.us',
'gender': 'Female',
ip_address: '180.66.162.255',
}, {
'id': 4,
'first_name': 'Willard',
'last_name': 'Valek',
email: 'wvalek3@vk.com',
'gender': 'Male',
ip_address: '67.76.188.26',
}];
render() {
return (<FoldableTable
data={this.getData()}
columns={[{
Header: "Name",
foldable: true,
columns: [
{
Header: "First Name",
accessor: "first_name"
},
{
Header: "Last Name",
accessor: "last_name"
}
]
}, {
Header: "Info",
foldable: true,
columns: [
{
Header: "Email",
accessor: "email"
},
{
Header: "Gender",
accessor: "gender"
}
]
}]
}></FoldableTable>)
}
}

View File

@ -0,0 +1,64 @@
import React from 'react';
import ReactTable from '../../../../lib/index';
import FoldableTableHOC from '../../../../lib/hoc/foldableTable';
const FoldableTable = FoldableTableHOC(ReactTable);
export default class FoldableTableWithoutHeader extends React.Component {
getData = () => [{
"id": 1,
"first_name": "Jeanette",
"last_name": "Penddreth",
"email": "jpenddreth0@census.gov",
"gender": "Female",
"ip_address": "26.58.193.2"
}, {
"id": 2,
"first_name": "Giavani",
"last_name": "Frediani",
"email": "gfrediani1@senate.gov",
"gender": "Male",
"ip_address": "229.179.4.212"
}, {
"id": 3,
"first_name": "Noell",
"last_name": "Bea",
"email": "nbea2@imageshack.us",
"gender": "Female",
"ip_address": "180.66.162.255"
}, {
"id": 4,
"first_name": "Willard",
"last_name": "Valek",
"email": "wvalek3@vk.com",
"gender": "Male",
"ip_address": "67.76.188.26"
}];
render() {
return <FoldableTable
data={this.getData()}
columns={[
{
Header: "First Name",
accessor: "first_name"
},
{
Header: "Last Name",
accessor: "last_name",
foldable: true,
},
{
Header: "Email",
accessor: "email",
foldable: true,
},
{
Header: "Gender",
accessor: "gender",
foldable: true,
}
]}></FoldableTable>
}
}

View File

@ -0,0 +1,30 @@
import React from 'react'
import '../../../../react-table.css'
import FoldableTableWithHeader from './FoldableTableWithHeader'
import FoldableTableWithoutHeader from './FoldableTableWithoutHeader'
import FoldableTableCustomState from './FoldableTableCustomState'
class FaldableComponentTest extends React.Component {
render() {
return (
<div>
<p>- Sample With Header Columns</p>
<br />
<FoldableTableWithHeader />
<br /> <br />
<p>- Sample With Normal Columns</p>
<br />
<FoldableTableWithoutHeader />
<br /> <br />
<p>- Custom State and selectedTable</p>
<br />
<FoldableTableCustomState />
</div >
)
}
}
export default FaldableComponentTest

View File

@ -1,7 +1,13 @@
/* eslint-disable */
import TreeTable from "./treetable";
import SelectTable from "./selecttable";
import SelectTreeTable from "./selecttreetable";
import TreeTable from './treetable'
import SelectTable from './selecttable'
import SelectTreeTable from './selecttreetable'
import FoldableTable from './foldabletable';
export { TreeTable, SelectTable, SelectTreeTable };
export {
TreeTable,
SelectTable,
SelectTreeTable,
FoldableTable
}

View File

@ -103,6 +103,107 @@ 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.
### FoldableTable
FoldableTable is a HOC that make the columns are foldable. The reason I developed this HOC because when working on the real project related to the financial which display so many columns for validation and comparison.
So foldable columns allow users to temporary hidden the unwanted to columns so that they can focus on the data that they want to see.
#### How it work
```javascfript
import ReactTable from 'react-table'
import FoldableTableHOC from 'react-table/lib/hoc/foldableTable'
const FoldableTable = FoldableTableHOC(ReactTable);
```
It will scan all the columns which `foldable` is `true` and apply the foldable column feature. This feature will work for both normal columns and header columns as samples below.
- With Header Columns
```javascript
render(){
return <FoldableTable
columns={[{
Header: "Name",
foldable: true,
columns: [{
Header: "First Name",
accessor: "first_name"
},{
Header: "Last Name",
accessor: "last_name"
}]
},{
Header: "Info",
foldable: true,
columns: [{
Header: "Email",
accessor: "email"
},{
Header: "Gender",
accessor: "gender"
}]
}]
}/>
}
```
![With Header Columns](https://raw.githubusercontent.com/baoduy/Images/master/Wordpress/JavaScripts/react-table%20foldableHOC/FoldableTable%20With%20Header.gif)
- With Nornal Columns
```javascript
render() {
return <FoldableTable
columns={[{
Header: "First Name",
accessor: "first_name"
},
{
Header: "Last Name",
accessor: "last_name",
foldable: true,
},
{
Header: "Email",
accessor: "email",
foldable: true,
},
{
Header: "Gender",
accessor: "gender",
foldable: true,
}]}></FoldableTable>
}
```
![With Normal Columns](https://raw.githubusercontent.com/baoduy/Images/master/Wordpress/JavaScripts/react-table%20foldableHOC/FoldableTable%20Without%20Header.gif)
- The `FoldableTable` also fully compatible with existing HOCs, below is with selectTableHOC.
![With Normal Columns](https://raw.githubusercontent.com/baoduy/Images/master/Wordpress/JavaScripts/react-table%20foldableHOC/FoldableTable%20With%20selectTable.gif)
#### State management
If you would like to manage the state of FoldableTable, then add the following codes.
```javascript
render() {
return <FoldableTable
onFoldChange={newFolded => this.setState(p => { return { folded: newFolded } })}
folded={this.state.folded}
/>
}
```
#### Custom Compoments
- FoldIconComponent: to render the Icon of buttons.
- FoldButtonComponent: to render the folding buttons for each Column.
With default rendering as below.
```javascript
const defaultFoldIconComponent = ({ collapsed }) => {
//Render your Icon here
}
const defaultFoldButtonComponent = ({ header, collapsed, icon, onClick }) => {
//Render your button here.
}
```
## 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

View File

@ -0,0 +1,225 @@
import React from 'react';
import left from './left.svg';
import right from './right.svg';
const defaultFoldIconComponent = ({ collapsed }) => {
const style = { width: 25 };
if (collapsed)
return <img src={right} style={style} alt="right" />
return <img src={left} style={style} alt="left" />
}
const defaultFoldButtonComponent = ({ header, collapsed, icon, onClick }) => {
const style = {
marginLeft: "0px",
marginTop: "-5px",
marginBottom: "-8px",
float: "left",
cursor: "pointer"
};
return (<div>
<div style={style} onClick={onClick}>
{icon}
</div>
{!collapsed && <div>{header}</div>}
</div>);
}
export default (ReactTable) => {
const wrapper = class RTFoldableTable extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
folded: props.onFoldChange ? undefined : {},
resized: props.resized || []
};
}
componentWillReceiveProps(newProps) {
if (this.state.resized !== newProps.resized)
this.setState(p => { return { resized: newProps.resized } });
}
onResizedChange = resized => {
const { onResizedChange } = this.props;
if (onResizedChange)
onResizedChange(resized);
else this.setState(p => { return { resized } });
}
removeResized = column => {
const { id } = column;
if (!id) return;
const { resized } = this.state;
if (!resized) return;
const rs = resized.find(r => r.id === id);
if (!rs) return;
const newResized = resized.filter(r => r !== rs);
this.onResizedChange(newResized);
}
// this is so we can expose the underlying ReactTable.
getWrappedInstance = () => {
if (!this.wrappedInstance) console.warn('RTFoldableTable - No wrapped instance');
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance();
else return this.wrappedInstance
}
getCopiedKey = key => {
const { foldableOriginalKey } = this.props;
return `${foldableOriginalKey}${key}`;
}
copyOriginals = column => {
const { FoldedColumn } = this.props;
//Stop copy if the column already copied
if (column.original_Header) return;
Object.keys(FoldedColumn).forEach(k => {
const copiedKey = this.getCopiedKey(k);
if (k === "Cell")
column[copiedKey] = column[k] ? column[k] : c => c.value;
else column[copiedKey] = column[k];
});
//Copy sub Columns
if (column.columns && !column.original_Columns)
column.original_Columns = column.columns;
//Copy Header
if (!column.original_Header)
column.original_Header = column.Header;
}
restoreToOriginal = column => {
const { FoldedColumn } = this.props;
Object.keys(FoldedColumn).forEach(k => {
//ignore header as handling by foldableHeaderRender
if (k === "Header") return;
const copiedKey = this.getCopiedKey(k);
column[k] = column[copiedKey];
});
if (column.columns && column.original_Columns)
column.columns = column.original_Columns;
}
getState = () => this.props.onFoldChange ? this.props.folded : this.state.folded;
isFolded = col => {
const folded = this.getState();
return folded[col.id] === true;
}
foldingHandler = col => {
if (!col || !col.id) return;
const { onFoldChange } = this.props;
const folded = this.getState();
const { id } = col;
let newFold = Object.assign({}, folded);
newFold[id] = !newFold[id];
//Remove the Resized if have
this.removeResized(col);
if (onFoldChange)
onFoldChange(newFold);
else this.setState(previous => { return { folded: newFold }; });
}
foldableHeaderRender = (cell) => {
const { FoldButtonComponent, FoldIconComponent } = this.props;
const { column } = cell;
const collapsed = this.isFolded(column);
const icon = React.createElement(FoldIconComponent, { collapsed });
const onClick = () => this.foldingHandler(column);
return React.createElement(FoldButtonComponent, { header: column.original_Header, collapsed, icon, onClick });
}
applyFoldableForColumn = column => {
const collapsed = this.isFolded(column);
const { FoldedColumn } = this.props;
//Handle Column Header
if (column.columns) {
if (collapsed) {
column.columns = [FoldedColumn];
column.width = FoldedColumn.width;
column.style = FoldedColumn.style;
}
else this.restoreToOriginal(column);
}
//Handle Normal Column.
else if (collapsed)
column = Object.assign(column, FoldedColumn);
else {
this.restoreToOriginal(column);
}
}
applyFoldableForColumns = columns => {
return columns.map((col, index) => {
if (!col.foldable) return col;
//If col don't have id then generate id based on index
if (!col.id)
col.id = `col_${index}`;
this.copyOriginals(col);
//Replace current header with internal header render.
col.Header = c => this.foldableHeaderRender(c);
//apply foldable
this.applyFoldableForColumn(col);
//return the new column out
return col;
});
}
render() {
const { columns: originalCols, FoldButtonComponent, FoldIconComponent, FoldedColumn, ...rest } = this.props;
const columns = this.applyFoldableForColumns([...originalCols]);
const extra = {
columns,
onResizedChange: this.onResizedChange,
resized: this.state.resized
};
return (
<ReactTable {...rest} {...extra} ref={r => this.wrappedInstance = r} />
)
}
}
wrapper.displayName = 'RTFoldableTable';
wrapper.defaultProps =
{
FoldIconComponent: defaultFoldIconComponent,
FoldButtonComponent: defaultFoldButtonComponent,
foldableOriginalKey: 'original_',
FoldedColumn: {
Cell: c => '',
width: 30,
sortable: false,
resizable: false,
filterable: false,
}
}
return wrapper;
}

View File

@ -0,0 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="512px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M327.3,98.9l-2.1,1.8l-156.5,136c-5.3,4.6-8.6,11.5-8.6,19.2c0,7.7,3.4,14.6,8.6,19.2L324.9,411l2.6,2.3 c2.5,1.7,5.5,2.7,8.7,2.7c8.7,0,15.8-7.4,15.8-16.6h0V112.6h0c0-9.2-7.1-16.6-15.8-16.6C332.9,96,329.8,97.1,327.3,98.9z"/></svg>

After

Width:  |  Height:  |  Size: 583 B

View File

@ -0,0 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="512px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M184.7,413.1l2.1-1.8l156.5-136c5.3-4.6,8.6-11.5,8.6-19.2c0-7.7-3.4-14.6-8.6-19.2L187.1,101l-2.6-2.3 C182,97,179,96,175.8,96c-8.7,0-15.8,7.4-15.8,16.6h0v286.8h0c0,9.2,7.1,16.6,15.8,16.6C179.1,416,182.2,414.9,184.7,413.1z"/></svg>

After

Width:  |  Height:  |  Size: 585 B