diff --git a/docs/src/App.js b/docs/src/App.js index c8bfcc3..3efcc39 100644 --- a/docs/src/App.js +++ b/docs/src/App.js @@ -21,12 +21,19 @@ import HOCReadme from './stories/HOCReadme.js' // { name: 'SelectTreeTable', component: SelectTreeTable }, // ] +import { TreeTable, CheckboxTable } from './examples/index' + +const exampleStories = [ + // examples + { name: 'TreeTable', component: TreeTable }, + { name: 'CheckboxTable', component: CheckboxTable }, +]; + 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', @@ -69,11 +76,9 @@ 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') }, - ] export default class App extends React.Component { diff --git a/docs/src/examples/checkbox/checkboxHOC.js b/docs/src/examples/checkbox/checkboxHOC.js new file mode 100644 index 0000000..0dd0091 --- /dev/null +++ b/docs/src/examples/checkbox/checkboxHOC.js @@ -0,0 +1,88 @@ + + +import React from 'react'; + +export default (Component) => { + + const wrapper = class RTCheckboxTable extends React.Component { + // we only need a Component so we can get the 'ref' - pure components can't get a 'ref' + + rowSelector = (row) => + { + if(!row || !row.hasOwnProperty(this.props.keyField)) return null; + const checked = this.props.isSelected(row[this.props.keyField]); + return ( + { + const { shiftKey } = e; + e.stopPropagation(); + this.props.toggleSelection(row[this.props.keyField],shiftKey,row); + }} + onChange={()=>{}} + value='' + /> + ); + } + + headSelector = (row) => + { + const checked = this.props.selectAll; + return ( + { + e.stopPropagation(); + this.props.toggleAll(); + }} + onChange={()=>{}} + value='' + /> + ); + } + + // this is so we can expose the underlying ReactTable to get at the sortedData for selectAll + getWrappedInstance = ()=>this.wrappedInstance + + render() + { + const { columns:originalCols, isSelected, toggleSelection, toggleAll, keyField, selectAll, ...rest } = this.props; + const { rowSelector, headSelector, } = this; + const select = { + id: '_selector', + accessor: ()=>'x', // this value is not important + Header: headSelector, + Cell: (ci) => { return rowSelector(ci.original); }, + width: 30, + filterable: false, + sortable: false, + resizable: false, + style: { textAlign: 'center' }, + } + const columns = [ + select, + ...originalCols, + ]; + const extra = { + columns, + }; + return ( + this.wrappedInstance=r}/> + ) + } + } + + wrapper.displayName = 'RTCheckboxTable'; + 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.') }, + } + + return wrapper; +} diff --git a/docs/src/examples/checkbox/index.js b/docs/src/examples/checkbox/index.js new file mode 100644 index 0000000..428fd4d --- /dev/null +++ b/docs/src/examples/checkbox/index.js @@ -0,0 +1,160 @@ + +import React from 'react'; +import shortid from 'shortid'; + +import ReactTable from '../../../../lib/index' +import '../../../../react-table.css' + +import checkboxTableHOC from './checkboxHOC'; + +const CheckboxTable = checkboxTableHOC(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, + }; + } + 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 + 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.checkboxTable.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)=>{ + 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); + } + render(){ + const { toggleSelection, toggleAll, isSelected, logSelection } = this; + const { data, columns, selectAll } = this.state; + const extraProps = + { + selectAll, + isSelected, + toggleAll, + toggleSelection, + } + return ( +
+

react-table - Checkbox Table

+ + {` (${this.state.selection.length}) selected`} + { + data? + this.checkboxTable = r} + className="-striped -highlight" + {...extraProps} + /> + :null + } +
+ ); + } +} + +// export default treeTableHOC(ComponentTest); +export default ComponentTest; diff --git a/docs/src/examples/treetable/treeTableHOC.js b/docs/src/examples/treetable/treeTableHOC.js new file mode 100644 index 0000000..078d808 --- /dev/null +++ b/docs/src/examples/treetable/treeTableHOC.js @@ -0,0 +1,58 @@ + + +import React from 'react'; + +export default (Component) => { + const wrapper = (componentProps) => { + const 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 = `${componentProps.treeTableIndent*ri.level}px`; + cell.props.style.backgroundColor = '#DDD'; + cell.props.style.borderBottom = '1px solid rgba(128,128,128,0.2)'; + + return
{cell}
; + } + return ; + } + + const getTrProps = (state,ri,ci,instance) => { + return {ri}; + } + + const { columns, ...rest } = componentProps; + const extra = { + columns: columns.map((col)=>{ + let column = col; + if(rest.pivotBy && rest.pivotBy.includes(col.accessor)) + { + column = { + accessor: col.accessor, + width: `${componentProps.treeTableIndent}px`, + show: false, + Header: '', + } + } + return column; + }), + TrComponent, + getTrProps, + }; + + return ( + + ) + } + wrapper.displayName = 'RTTreeTable'; + wrapper.defaultProps = + { + treeTableRowBackground: '#EEE', + treeTableIndent: 10, + } + return wrapper; +}