mirror of
https://github.com/gosticks/react-table.git
synced 2025-10-16 11:55:36 +00:00
Init v7 src
This commit is contained in:
parent
a99ecbd567
commit
2a6dfb6ebd
@ -1,77 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 8,
|
|
||||||
ecmaFeatures: {
|
|
||||||
experimentalObjectRestSpread: true,
|
|
||||||
jsx: true,
|
|
||||||
node: false,
|
|
||||||
classes: true
|
|
||||||
},
|
|
||||||
sourceType: 'module'
|
|
||||||
},
|
|
||||||
|
|
||||||
parser: 'babel-eslint',
|
|
||||||
|
|
||||||
extends: ['standard'],
|
|
||||||
|
|
||||||
plugins: ['react'],
|
|
||||||
|
|
||||||
rules: {
|
|
||||||
// Nozzle
|
|
||||||
'jsx-quotes': [2, 'prefer-single'],
|
|
||||||
'comma-dangle': [2, 'always-multiline'],
|
|
||||||
|
|
||||||
// // React
|
|
||||||
'react/jsx-boolean-value': 2,
|
|
||||||
'react/jsx-curly-spacing': [2, 'never'],
|
|
||||||
'react/jsx-equals-spacing': [2, 'never'],
|
|
||||||
// 'react/jsx-indent': 2,
|
|
||||||
'react/jsx-indent-props': [2, 2],
|
|
||||||
'react/jsx-no-duplicate-props': 2,
|
|
||||||
'react/jsx-no-undef': 2,
|
|
||||||
'react/jsx-tag-spacing': [
|
|
||||||
2,
|
|
||||||
{
|
|
||||||
closingSlash: 'never',
|
|
||||||
beforeSelfClosing: 'always',
|
|
||||||
afterOpening: 'never'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'react/jsx-uses-react': 2,
|
|
||||||
'react/jsx-uses-vars': 2,
|
|
||||||
'react/self-closing-comp': 2,
|
|
||||||
'react/jsx-no-bind': [
|
|
||||||
2,
|
|
||||||
{
|
|
||||||
allowArrowFunctions: true,
|
|
||||||
allowBind: false,
|
|
||||||
ignoreRefs: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'react/no-did-update-set-state': 2,
|
|
||||||
'react/no-unknown-property': 2,
|
|
||||||
'react/react-in-jsx-scope': 2,
|
|
||||||
'react/jsx-closing-bracket-location': [0, 'tag-aligned'],
|
|
||||||
'react/jsx-tag-spacing': [2, { beforeSelfClosing: 'always' }],
|
|
||||||
'react/jsx-wrap-multilines': 2,
|
|
||||||
'react/self-closing-comp': 2,
|
|
||||||
'react/jsx-key': 2,
|
|
||||||
'react/jsx-no-comment-textnodes': 2,
|
|
||||||
'react/jsx-no-duplicate-props': 2,
|
|
||||||
'react/jsx-no-target-blank': 2,
|
|
||||||
'react/jsx-no-undef': 2,
|
|
||||||
'react/jsx-uses-react': 2,
|
|
||||||
'react/jsx-uses-vars': 2,
|
|
||||||
'react/no-danger-with-children': 2,
|
|
||||||
'react/no-deprecated': 2,
|
|
||||||
'react/no-direct-mutation-state': 2,
|
|
||||||
'react/no-find-dom-node': 2,
|
|
||||||
'react/no-is-mounted': 2,
|
|
||||||
'react/no-render-return-value': 2,
|
|
||||||
'react/no-string-refs': 2,
|
|
||||||
'react/no-unknown-property': 2,
|
|
||||||
'react/react-in-jsx-scope': 2,
|
|
||||||
'react/require-render-return': 2
|
|
||||||
// 'react/jsx-max-props-per-line': [2, { maximum: 1 }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
react-table.js.org
|
|
||||||
1628
docs/README.md
1628
docs/README.md
File diff suppressed because it is too large
Load Diff
@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "new",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"devDependencies": {
|
|
||||||
"autoprefixer": "^6.7.0",
|
|
||||||
"babel-cli": "6.14.0",
|
|
||||||
"babel-eslint": "6.1.2",
|
|
||||||
"babel-preset-es2015": "6.14.0",
|
|
||||||
"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",
|
|
||||||
"postcss-cli": "^2.6.0",
|
|
||||||
"react": "^15.4.2",
|
|
||||||
"react-dom": "^15.4.2",
|
|
||||||
"react-json-tree": "^0.10.9",
|
|
||||||
"rimraf": "^2.6.1",
|
|
||||||
"standard": "^10.0.2",
|
|
||||||
"stylus": "^0.54.5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"eslint-config-react-tools": "^1.0.10",
|
|
||||||
"github-markdown-css": "^2.6.0",
|
|
||||||
"marked": "^0.3.6",
|
|
||||||
"namor": "^1.0.1",
|
|
||||||
"raw-loader": "^0.5.1",
|
|
||||||
"react": "^15.5.4",
|
|
||||||
"react-dom": "^15.5.4",
|
|
||||||
"react-json-tree": "^0.10.9",
|
|
||||||
"react-script": "^2.0.5",
|
|
||||||
"react-scripts": "^0.9.5",
|
|
||||||
"react-story": "^0.0.10",
|
|
||||||
"shortid": "^2.2.8"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "react-scripts start",
|
|
||||||
"build": "react-scripts build",
|
|
||||||
"test": "react-scripts test --env=jsdom",
|
|
||||||
"eject": "react-scripts eject"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
@ -1,31 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tag above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>React Table - A lightweight, fast and extendable datagrid built for React</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start`.
|
|
||||||
To create a production bundle, use `npm run build`.
|
|
||||||
-->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
120
docs/src/App.js
120
docs/src/App.js
@ -1,120 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
//
|
|
||||||
import ReactStory, { defaultProps } from 'react-story';
|
|
||||||
import CodeSandbox from './CodeSandbox.js';
|
|
||||||
import './stories/utils/prism.css';
|
|
||||||
import '../../react-table.css';
|
|
||||||
|
|
||||||
import Readme from './stories/Readme.js';
|
|
||||||
import HOCReadme from './stories/HOCReadme.js';
|
|
||||||
|
|
||||||
// import Test from './stories/test.js'
|
|
||||||
|
|
||||||
// import Tester from './examples/expander';
|
|
||||||
|
|
||||||
const stories = [
|
|
||||||
{ name: 'Readme', component: Readme },
|
|
||||||
{ name: 'HOC Readme', component: HOCReadme },
|
|
||||||
|
|
||||||
// { name: 'Tester', component: Test },
|
|
||||||
{ name: 'Simple Table', component: CodeSandbox('X6npLXPRW') },
|
|
||||||
{
|
|
||||||
name: 'Cell Renderers & Custom Components',
|
|
||||||
component: CodeSandbox('OyRL04Z4Y')
|
|
||||||
},
|
|
||||||
{ name: 'Default Sorting', component: CodeSandbox('gLwmmjzA3') },
|
|
||||||
{
|
|
||||||
name: 'Custom Sorting',
|
|
||||||
component: CodeSandbox('VGx67J35')
|
|
||||||
},
|
|
||||||
{ name: 'Custom Column Widths', component: CodeSandbox('o2OORXNXN') },
|
|
||||||
{ name: 'Custom Component Props', component: CodeSandbox('nZW3L0wp4') },
|
|
||||||
{ name: 'Server-side Data', component: CodeSandbox('wjrn8wy3R') },
|
|
||||||
{ name: 'Sub Components', component: CodeSandbox('n2gqAxl7') },
|
|
||||||
{ name: 'Pivoting & Aggregation', component: CodeSandbox('oNY9z8xN') },
|
|
||||||
{
|
|
||||||
name: 'Pivoting & Aggregation w/ Sub Components',
|
|
||||||
component: CodeSandbox('p0kEVBgQ')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '100k Rows w/ Pivoting & Sub Components',
|
|
||||||
component: CodeSandbox('DRmKj0XyK')
|
|
||||||
},
|
|
||||||
{ name: 'Pivoting Options', component: CodeSandbox('kZKmNBK6r') },
|
|
||||||
{ name: 'Functional Rendering', component: CodeSandbox('VPZ0Bzv8X') },
|
|
||||||
{
|
|
||||||
name: 'Custom Expander Position',
|
|
||||||
component: CodeSandbox('1jj2XrPEV')
|
|
||||||
},
|
|
||||||
{ name: 'Custom "No Data" Text', component: CodeSandbox('RgRpRDv80') },
|
|
||||||
{ name: 'Footers', component: CodeSandbox('KOqQXn3p8') },
|
|
||||||
{ name: 'Custom Filtering', component: CodeSandbox('5Eyxxxyx') },
|
|
||||||
{ name: 'Controlled Component', component: CodeSandbox('r7XEZRK2') },
|
|
||||||
{ name: 'Editable Table', component: CodeSandbox('n5r19gzQP') },
|
|
||||||
{
|
|
||||||
name: 'Fixed Header w/ Vertical Scroll',
|
|
||||||
component: CodeSandbox('7LY0gjA8O')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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: 'Foldable Table (HOC)', component: CodeSandbox('8pkrj5yorl') },
|
|
||||||
{ name: 'Advanced Expand Table (HOC)', component: CodeSandbox('y2m39jz8v1') }
|
|
||||||
];
|
|
||||||
|
|
||||||
export default class App extends React.Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<ReactStory
|
|
||||||
style={{
|
|
||||||
display: 'block',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%'
|
|
||||||
}}
|
|
||||||
pathPrefix="story/"
|
|
||||||
StoryWrapper={props => (
|
|
||||||
<defaultProps.StoryWrapper
|
|
||||||
css={{
|
|
||||||
padding: 0,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="//github.com/react-tools/react-table"
|
|
||||||
style={{
|
|
||||||
display: 'block',
|
|
||||||
textAlign: 'center',
|
|
||||||
borderBottom: 'solid 3px #cccccc'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="https://github.com/react-tools/media/raw/master/logo-react-table.png"
|
|
||||||
alt="React Table Logo"
|
|
||||||
style={{
|
|
||||||
width: '150px',
|
|
||||||
padding: '10px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<div
|
|
||||||
{...props}
|
|
||||||
style={{
|
|
||||||
flex: '1 0 auto',
|
|
||||||
position: 'relative'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</defaultProps.StoryWrapper>
|
|
||||||
)}
|
|
||||||
stories={stories}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
export default id => () => {
|
|
||||||
return (
|
|
||||||
<iframe
|
|
||||||
src={`https://codesandbox.io/embed/${id}?autoresize=1&hidenavigation=1&view=${
|
|
||||||
global.innerWidth < 1000 ? "preview" : "split"
|
|
||||||
}`}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: `100%`,
|
|
||||||
height: `100%`,
|
|
||||||
border: 0,
|
|
||||||
overflow: `hidden`
|
|
||||||
}}
|
|
||||||
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render } from 'react-dom';
|
|
||||||
import { makeData, Logo, Tips } from './Utils';
|
|
||||||
import { advancedExpandTableHOC } from '../../../../lib/hoc/advancedExpandTable';
|
|
||||||
|
|
||||||
// Import React Table
|
|
||||||
import ReactTable from 'react-table';
|
|
||||||
import 'react-table/react-table.css';
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
Header: 'First Name',
|
|
||||||
accessor: 'firstName',
|
|
||||||
Cell: props => {
|
|
||||||
const {
|
|
||||||
// react table props
|
|
||||||
columnProps: { rest: { showRowSubComponent } },
|
|
||||||
nestingPath
|
|
||||||
} = props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
onClick={e => showRowSubComponent({ nestingPath: nestingPath }, e)}
|
|
||||||
>
|
|
||||||
SHOW SUBCOMPONENT {props.value}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Last Name',
|
|
||||||
id: 'lastName',
|
|
||||||
accessor: d => d.lastName
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Info',
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
Header: 'Age',
|
|
||||||
accessor: 'age',
|
|
||||||
Cell: props => {
|
|
||||||
const {
|
|
||||||
// react table props
|
|
||||||
columnProps: { rest: { toggleRowSubComponent } },
|
|
||||||
nestingPath
|
|
||||||
} = props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
onClick={e =>
|
|
||||||
toggleRowSubComponent({ nestingPath: nestingPath }, e)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
TOGGLE SUBCOMPONENT {props.value}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Status',
|
|
||||||
accessor: 'status'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Stats',
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
Header: 'Visits',
|
|
||||||
accessor: 'visits'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const AdvancedExpandReactTable = advancedExpandTableHOC(ReactTable);
|
|
||||||
|
|
||||||
class App extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
data: makeData()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
const { data } = this.state;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<AdvancedExpandReactTable
|
|
||||||
data={data}
|
|
||||||
columns={columns}
|
|
||||||
defaultPageSize={10}
|
|
||||||
className='-striped -highlight'
|
|
||||||
SubComponent={({ row, nestingPath, toggleRowSubComponent }) => {
|
|
||||||
return (
|
|
||||||
<div style={{ padding: '20px' }}>
|
|
||||||
<button
|
|
||||||
onClick={e => toggleRowSubComponent({ nestingPath }, e)}
|
|
||||||
>
|
|
||||||
CLOSE SUBCOMPONENT {row.firstName} {row.lastName}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<Tips />
|
|
||||||
<Logo />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render(<App />, document.getElementById('root'));
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
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 (
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={checked}
|
|
||||||
onClick={e => {
|
|
||||||
const { shiftKey } = e;
|
|
||||||
e.stopPropagation();
|
|
||||||
this.props.toggleSelection(row[this.props.keyField], shiftKey, row);
|
|
||||||
}}
|
|
||||||
onChange={() => {}}
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
headSelector = row => {
|
|
||||||
const checked = this.props.selectAll;
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={checked}
|
|
||||||
onClick={e => {
|
|
||||||
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 (
|
|
||||||
<Component {...rest} {...extra} ref={r => (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;
|
|
||||||
};
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
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 (
|
|
||||||
<div style={{ padding: "10px" }}>
|
|
||||||
<h1>react-table - Checkbox Table</h1>
|
|
||||||
<button onClick={logSelection}>Log Selection to Console</button>
|
|
||||||
{` (${this.state.selection.length}) selected`}
|
|
||||||
{data ? (
|
|
||||||
<CheckboxTable
|
|
||||||
data={data}
|
|
||||||
columns={columns}
|
|
||||||
ref={r => (this.checkboxTable = r)}
|
|
||||||
className="-striped -highlight"
|
|
||||||
{...extraProps}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// export default treeTableHOC(ComponentTest);
|
|
||||||
export default ComponentTest;
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
export default class ComponentTest extends React.component {
|
|
||||||
render() {
|
|
||||||
return <div>Bozo</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// import ReactTable from '../../../../lib/index'
|
|
||||||
// import '../../../../react-table.css'
|
|
||||||
//
|
|
||||||
// console.log('ReactTable:',ReactTable)
|
|
||||||
//
|
|
||||||
// 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' : ''}`}>
|
|
||||||
// •
|
|
||||||
// </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, closeExpanded)=><ExpanderComponent {...props, closeExpanded} />,
|
|
||||||
// // SubComponent: (props)=><SubComponent {...props} />,
|
|
||||||
// // multiSort: false,
|
|
||||||
// }
|
|
||||||
// return (
|
|
||||||
// <ReactTable
|
|
||||||
// {...rtProps}
|
|
||||||
// />
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
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>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
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>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
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>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
|
|
||||||
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
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
import TreeTable from './treetable'
|
|
||||||
import SelectTable from './selecttable'
|
|
||||||
import SelectTreeTable from './selecttreetable'
|
|
||||||
import FoldableTable from './foldabletable';
|
|
||||||
|
|
||||||
export {
|
|
||||||
TreeTable,
|
|
||||||
SelectTable,
|
|
||||||
SelectTreeTable,
|
|
||||||
FoldableTable
|
|
||||||
}
|
|
||||||
@ -1,173 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,238 +0,0 @@
|
|||||||
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,70 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import ReactTable from "../../../../lib/index";
|
|
||||||
import "../../../../react-table.css";
|
|
||||||
|
|
||||||
import treeTableHOC from "../../../../lib/hoc/treeTable";
|
|
||||||
|
|
||||||
async function getData() {
|
|
||||||
const result = await (await fetch("/au_500_tree.json")).json();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getColumns(data, pivotBy) {
|
|
||||||
const columns = [];
|
|
||||||
const sample = data[0];
|
|
||||||
for (let key in sample) {
|
|
||||||
columns.push({
|
|
||||||
accessor: key,
|
|
||||||
Header: key
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComponentTest extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
data: null,
|
|
||||||
columns: null,
|
|
||||||
pivotBy: null // ["firstName", "lastName"],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
componentDidMount() {
|
|
||||||
getData().then(data => {
|
|
||||||
// console.log('cwm data:',data);
|
|
||||||
const pivotBy = ["state", "post", "city"];
|
|
||||||
const columns = getColumns(data, pivotBy);
|
|
||||||
// console.log('cwm cols:',columns);
|
|
||||||
this.setState({ data, columns, pivotBy });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
showState = () => {
|
|
||||||
console.log("state:", this.reactTable.getResolvedState());
|
|
||||||
};
|
|
||||||
render() {
|
|
||||||
const { data, columns, pivotBy } = this.state;
|
|
||||||
const extraProps = {
|
|
||||||
data,
|
|
||||||
columns,
|
|
||||||
pivotBy
|
|
||||||
};
|
|
||||||
const TreeTable = treeTableHOC(ReactTable);
|
|
||||||
return (
|
|
||||||
<div style={{ padding: "10px" }}>
|
|
||||||
<h1>react-table - Tree Table</h1>
|
|
||||||
{data ? (
|
|
||||||
<TreeTable
|
|
||||||
ref={r => (this.reactTable = r)}
|
|
||||||
className="-striped -highlight"
|
|
||||||
defaultPageSize={5}
|
|
||||||
{...extraProps}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ComponentTest;
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
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 <div {...rest}>{cell}</div>;
|
|
||||||
}
|
|
||||||
return <Component.defaultProps.TrComponent {...rest} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 <Component {...rest} {...extra} />;
|
|
||||||
};
|
|
||||||
wrapper.displayName = "RTTreeTable";
|
|
||||||
wrapper.defaultProps = {
|
|
||||||
treeTableRowBackground: "#EEE",
|
|
||||||
treeTableIndent: 10
|
|
||||||
};
|
|
||||||
return wrapper;
|
|
||||||
};
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import namor from "namor";
|
|
||||||
|
|
||||||
const range = len => {
|
|
||||||
const arr = [];
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
arr.push(i);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
};
|
|
||||||
|
|
||||||
const newPerson = () => {
|
|
||||||
const statusChance = Math.random();
|
|
||||||
return {
|
|
||||||
firstName: namor.generate({ words: 1, numbers: 0 }),
|
|
||||||
lastName: namor.generate({ words: 1, numbers: 0 }),
|
|
||||||
age: Math.floor(Math.random() * 30),
|
|
||||||
visits: Math.floor(Math.random() * 100),
|
|
||||||
progress: Math.floor(Math.random() * 100),
|
|
||||||
status:
|
|
||||||
statusChance > 0.66
|
|
||||||
? "relationship"
|
|
||||||
: statusChance > 0.33
|
|
||||||
? "complicated"
|
|
||||||
: "single"
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function makeData(len = 5553) {
|
|
||||||
return range(len).map(d => {
|
|
||||||
return {
|
|
||||||
...newPerson(),
|
|
||||||
children: range(10).map(newPerson)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
html,
|
|
||||||
body,
|
|
||||||
#root {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom";
|
|
||||||
//
|
|
||||||
import "./index.css";
|
|
||||||
import App from "./App.js";
|
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById("root"));
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
/* 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
/* eslint-disable import/no-webpack-loader-syntax */
|
|
||||||
import React from "react";
|
|
||||||
import marked from "marked";
|
|
||||||
//
|
|
||||||
import Readme from "!raw!../../../README.md";
|
|
||||||
import "github-markdown-css/github-markdown.css";
|
|
||||||
import "./utils/prism.js";
|
|
||||||
|
|
||||||
export default class Story extends React.Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div style={{ padding: "10px" }}>
|
|
||||||
<span
|
|
||||||
className="markdown-body"
|
|
||||||
dangerouslySetInnerHTML={{ __html: marked(Readme) }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
componentDidMount() {
|
|
||||||
global.Prism && global.Prism.highlightAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
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" }
|
|
||||||
];
|
|
||||||
|
|
||||||
export default class Story extends React.Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
Test
|
|
||||||
<ReactTable data={data} columns={columns} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
|
|
||||||
/**
|
|
||||||
* prism.js default theme for JavaScript, CSS and HTML
|
|
||||||
* Based on dabblet (http://dabblet.com)
|
|
||||||
* @author Lea Verou
|
|
||||||
*/
|
|
||||||
|
|
||||||
code[class*="language-"],
|
|
||||||
pre[class*="language-"] {
|
|
||||||
color: black;
|
|
||||||
background: none;
|
|
||||||
text-shadow: 0 1px white;
|
|
||||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
|
||||||
text-align: left;
|
|
||||||
white-space: pre;
|
|
||||||
word-spacing: normal;
|
|
||||||
word-break: normal;
|
|
||||||
word-wrap: normal;
|
|
||||||
line-height: 1.5;
|
|
||||||
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
-o-tab-size: 4;
|
|
||||||
tab-size: 4;
|
|
||||||
|
|
||||||
-webkit-hyphens: none;
|
|
||||||
-moz-hyphens: none;
|
|
||||||
-ms-hyphens: none;
|
|
||||||
hyphens: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
|
||||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
|
||||||
text-shadow: none;
|
|
||||||
background: #b3d4fc;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
|
||||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
|
||||||
text-shadow: none;
|
|
||||||
background: #b3d4fc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
code[class*="language-"],
|
|
||||||
pre[class*="language-"] {
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Code blocks */
|
|
||||||
pre[class*="language-"] {
|
|
||||||
padding: 1em;
|
|
||||||
margin: .5em 0;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:not(pre) > code[class*="language-"],
|
|
||||||
pre[class*="language-"] {
|
|
||||||
background: #f5f2f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Inline code */
|
|
||||||
:not(pre) > code[class*="language-"] {
|
|
||||||
padding: .1em;
|
|
||||||
border-radius: .3em;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.comment,
|
|
||||||
.token.prolog,
|
|
||||||
.token.doctype,
|
|
||||||
.token.cdata {
|
|
||||||
color: slategray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.punctuation {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.namespace {
|
|
||||||
opacity: .7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.property,
|
|
||||||
.token.tag,
|
|
||||||
.token.boolean,
|
|
||||||
.token.number,
|
|
||||||
.token.constant,
|
|
||||||
.token.symbol,
|
|
||||||
.token.deleted {
|
|
||||||
color: #905;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.selector,
|
|
||||||
.token.attr-name,
|
|
||||||
.token.string,
|
|
||||||
.token.char,
|
|
||||||
.token.builtin,
|
|
||||||
.token.inserted {
|
|
||||||
color: #690;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.operator,
|
|
||||||
.token.entity,
|
|
||||||
.token.url,
|
|
||||||
.language-css .token.string,
|
|
||||||
.style .token.string {
|
|
||||||
color: #a67f59;
|
|
||||||
background: hsla(0, 0%, 100%, .5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.atrule,
|
|
||||||
.token.attr-value,
|
|
||||||
.token.keyword {
|
|
||||||
color: #07a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.function {
|
|
||||||
color: #DD4A68;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.regex,
|
|
||||||
.token.important,
|
|
||||||
.token.variable {
|
|
||||||
color: #e90;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.important,
|
|
||||||
.token.bold {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.token.italic {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token.entity {
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
7301
docs/yarn.lock
7301
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
BIN
media/Banner.png
BIN
media/Banner.png
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
@ -3,10 +3,10 @@
|
|||||||
"version": "6.9.0",
|
"version": "6.9.0",
|
||||||
"description": "A fast, lightweight, opinionated table and datagrid built on React",
|
"description": "A fast, lightweight, opinionated table and datagrid built on React",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/react-tools/react-table#readme",
|
"homepage": "https://github.com/tannerlinsley/react-table#readme",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/react-tools/react-table.git"
|
"url": "git+https://github.com/tannerlinsley/react-table.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
|
|||||||
7
src/aggregations.js
Executable file
7
src/aggregations.js
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
export function sum (values, rows) {
|
||||||
|
return values.reduce((sum, next) => sum + next, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function average (values, rows) {
|
||||||
|
return Math.round((sum(values, rows) / values.length) * 100) / 100
|
||||||
|
}
|
||||||
@ -1,270 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
//
|
|
||||||
import _ from './utils'
|
|
||||||
import Pagination from './pagination'
|
|
||||||
|
|
||||||
const emptyObj = () => ({})
|
|
||||||
|
|
||||||
export default {
|
|
||||||
// General
|
|
||||||
data: [],
|
|
||||||
resolveData: data => data,
|
|
||||||
loading: false,
|
|
||||||
showPagination: true,
|
|
||||||
showPaginationTop: false,
|
|
||||||
showPaginationBottom: true,
|
|
||||||
showPageSizeOptions: true,
|
|
||||||
pageSizeOptions: [5, 10, 20, 25, 50, 100],
|
|
||||||
defaultPage: 0,
|
|
||||||
defaultPageSize: 20,
|
|
||||||
showPageJump: true,
|
|
||||||
collapseOnSortingChange: true,
|
|
||||||
collapseOnPageChange: true,
|
|
||||||
collapseOnDataChange: true,
|
|
||||||
freezeWhenExpanded: false,
|
|
||||||
sortable: true,
|
|
||||||
multiSort: true,
|
|
||||||
resizable: true,
|
|
||||||
filterable: false,
|
|
||||||
defaultSortDesc: false,
|
|
||||||
defaultSorted: [],
|
|
||||||
defaultFiltered: [],
|
|
||||||
defaultResized: [],
|
|
||||||
defaultExpanded: {},
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
defaultFilterMethod: (filter, row, column) => {
|
|
||||||
const id = filter.pivotId || filter.id
|
|
||||||
return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
defaultSortMethod: (a, b, desc) => {
|
|
||||||
// force null and undefined to the bottom
|
|
||||||
a = a === null || a === undefined ? '' : a
|
|
||||||
b = b === null || b === undefined ? '' : b
|
|
||||||
// force any string values to lowercase
|
|
||||||
a = typeof a === 'string' ? a.toLowerCase() : a
|
|
||||||
b = typeof b === 'string' ? b.toLowerCase() : b
|
|
||||||
// Return either 1 or -1 to indicate a sort priority
|
|
||||||
if (a > b) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if (a < b) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
// returning 0, undefined or any falsey value will use subsequent sorts or
|
|
||||||
// the index as a tiebreaker
|
|
||||||
return 0
|
|
||||||
},
|
|
||||||
|
|
||||||
// Controlled State Props
|
|
||||||
// page: undefined,
|
|
||||||
// pageSize: undefined,
|
|
||||||
// sorted: [],
|
|
||||||
// filtered: [],
|
|
||||||
// resized: [],
|
|
||||||
// expanded: {},
|
|
||||||
|
|
||||||
// Controlled State Callbacks
|
|
||||||
onPageChange: undefined,
|
|
||||||
onPageSizeChange: undefined,
|
|
||||||
onSortedChange: undefined,
|
|
||||||
onFilteredChange: undefined,
|
|
||||||
onResizedChange: undefined,
|
|
||||||
onExpandedChange: undefined,
|
|
||||||
|
|
||||||
// Pivoting
|
|
||||||
pivotBy: undefined,
|
|
||||||
|
|
||||||
// Key Constants
|
|
||||||
pivotValKey: '_pivotVal',
|
|
||||||
pivotIDKey: '_pivotID',
|
|
||||||
subRowsKey: '_subRows',
|
|
||||||
aggregatedKey: '_aggregated',
|
|
||||||
nestingLevelKey: '_nestingLevel',
|
|
||||||
originalKey: '_original',
|
|
||||||
indexKey: '_index',
|
|
||||||
groupedByPivotKey: '_groupedByPivot',
|
|
||||||
|
|
||||||
// Server-side Callbacks
|
|
||||||
onFetchData: () => 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: {
|
|
||||||
// Renderers
|
|
||||||
Cell: undefined,
|
|
||||||
Header: undefined,
|
|
||||||
Footer: undefined,
|
|
||||||
Aggregated: undefined,
|
|
||||||
Pivot: undefined,
|
|
||||||
PivotValue: undefined,
|
|
||||||
Expander: undefined,
|
|
||||||
Filter: undefined,
|
|
||||||
Placeholder: undefined,
|
|
||||||
// All Columns
|
|
||||||
sortable: undefined, // use table default
|
|
||||||
resizable: undefined, // use table default
|
|
||||||
filterable: undefined, // use table default
|
|
||||||
show: true,
|
|
||||||
minWidth: 100,
|
|
||||||
minResizeWidth: 11,
|
|
||||||
// Cells only
|
|
||||||
className: '',
|
|
||||||
style: {},
|
|
||||||
getProps: emptyObj,
|
|
||||||
// Pivot only
|
|
||||||
aggregate: undefined,
|
|
||||||
// Headers only
|
|
||||||
headerClassName: '',
|
|
||||||
headerStyle: {},
|
|
||||||
getHeaderProps: emptyObj,
|
|
||||||
// Footers only
|
|
||||||
footerClassName: '',
|
|
||||||
footerStyle: {},
|
|
||||||
getFooterProps: emptyObj,
|
|
||||||
filterMethod: undefined,
|
|
||||||
filterAll: false,
|
|
||||||
sortMethod: undefined,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Global Expander Column Defaults
|
|
||||||
expanderDefaults: {
|
|
||||||
sortable: false,
|
|
||||||
resizable: false,
|
|
||||||
filterable: false,
|
|
||||||
width: 35,
|
|
||||||
},
|
|
||||||
|
|
||||||
pivotDefaults: {
|
|
||||||
// extend the defaults for pivoted columns here
|
|
||||||
},
|
|
||||||
|
|
||||||
// Text
|
|
||||||
previousText: 'Previous',
|
|
||||||
nextText: 'Next',
|
|
||||||
loadingText: 'Loading...',
|
|
||||||
noDataText: 'No rows found',
|
|
||||||
pageText: 'Page',
|
|
||||||
ofText: 'of',
|
|
||||||
rowsText: 'rows',
|
|
||||||
pageJumpText: 'jump to page',
|
|
||||||
rowsSelectorText: 'rows per page',
|
|
||||||
|
|
||||||
// Components
|
|
||||||
TableComponent: ({ children, className, ...rest }) => (
|
|
||||||
<div
|
|
||||||
className={classnames('rt-table', className)}
|
|
||||||
role="grid"
|
|
||||||
// tabIndex='0'
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
TheadComponent: _.makeTemplateComponent('rt-thead', 'Thead'),
|
|
||||||
TbodyComponent: _.makeTemplateComponent('rt-tbody', 'Tbody'),
|
|
||||||
TrGroupComponent: ({ children, className, ...rest }) => (
|
|
||||||
<div className={classnames('rt-tr-group', className)} role="rowgroup" {...rest}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
TrComponent: ({ children, className, ...rest }) => (
|
|
||||||
<div className={classnames('rt-tr', className)} role="row" {...rest}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
ThComponent: ({
|
|
||||||
toggleSort, className, children, ...rest
|
|
||||||
}) => (
|
|
||||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
|
|
||||||
<div
|
|
||||||
className={classnames('rt-th', className)}
|
|
||||||
onClick={e => toggleSort && toggleSort(e)}
|
|
||||||
role="columnheader"
|
|
||||||
tabIndex="-1" // Resolves eslint issues without implementing keyboard navigation incorrectly
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
TdComponent: ({
|
|
||||||
toggleSort, className, children, ...rest
|
|
||||||
}) => (
|
|
||||||
<div className={classnames('rt-td', className)} role="gridcell" {...rest}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
TfootComponent: _.makeTemplateComponent('rt-tfoot', 'Tfoot'),
|
|
||||||
FilterComponent: ({ filter, onChange, column }) => (
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
}}
|
|
||||||
placeholder={column.Placeholder}
|
|
||||||
value={filter ? filter.value : ''}
|
|
||||||
onChange={event => onChange(event.target.value)}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
ExpanderComponent: ({ isExpanded }) => (
|
|
||||||
<div className={classnames('rt-expander', isExpanded && '-open')}>•</div>
|
|
||||||
),
|
|
||||||
PivotValueComponent: ({ subRows, value }) => (
|
|
||||||
<span>
|
|
||||||
{value} {subRows && `(${subRows.length})`}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
AggregatedComponent: ({ subRows, column }) => {
|
|
||||||
const previewValues = subRows.filter(d => typeof d[column.id] !== 'undefined').map((row, i) => (
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
|
||||||
<span key={i}>
|
|
||||||
{row[column.id]}
|
|
||||||
{i < subRows.length - 1 ? ', ' : ''}
|
|
||||||
</span>
|
|
||||||
))
|
|
||||||
return <span>{previewValues}</span>
|
|
||||||
},
|
|
||||||
PivotComponent: undefined, // this is a computed default generated using
|
|
||||||
// the ExpanderComponent and PivotValueComponent at run-time in methods.js
|
|
||||||
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', 'NoData'),
|
|
||||||
ResizerComponent: _.makeTemplateComponent('rt-resizer', 'Resizer'),
|
|
||||||
PadRowComponent: () => <span> </span>,
|
|
||||||
}
|
|
||||||
@ -1,316 +0,0 @@
|
|||||||
<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
|
|
||||||
|
|
||||||
Any of the below HOCs can be imported from react-table like so:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import ReactTable from "react-table";
|
|
||||||
import treeTableHOC from "react-table/lib/hoc/treeTable";
|
|
||||||
|
|
||||||
const TreeTable = treeTableHOC(ReactTable);
|
|
||||||
```
|
|
||||||
|
|
||||||
Swap `treeTable` and `TreeTable` with any of the other HOC names as necessary.
|
|
||||||
|
|
||||||
### 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
|
|
||||||
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
|
|
||||||
|
|
||||||
**Note:** The select field defaults to the accessor `_id` property in order to render the select field for that particular row. If your objects have different
|
|
||||||
unique ID fields, make sure to tell React Table that by passing it the `keyField` property.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
<ReactTable keyField='id' />
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
### 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 works
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
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 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>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- The `FoldableTable` also fully compatible with existing HOCs, below is with selectTableHOC.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
#### 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 Components
|
|
||||||
- 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.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### AdvancedExpandTable
|
|
||||||
|
|
||||||
HOC which allows any Cell in the row to toggle the row's
|
|
||||||
SubComponent (expand/collapse). Also allows the SubComponent to toggle itself. Technically supports toggling any row's SubComponent.
|
|
||||||
|
|
||||||
These are the expand functions available to any SubComponent or Column Cell:
|
|
||||||
|
|
||||||
```
|
|
||||||
toggleRowSubComponent
|
|
||||||
showRowSubComponent
|
|
||||||
hideRowSubComponent
|
|
||||||
```
|
|
||||||
|
|
||||||
They are available through the `props.columnProps.rest` object.
|
|
||||||
|
|
||||||
On any change to the props, the table will reset the expanded state.
|
|
||||||
|
|
||||||
Accepts a onExpandedChange callback to be called whenever the table expanded state changes
|
|
||||||
|
|
||||||
Note: only supports 1 level of nesting.
|
|
||||||
|
|
||||||
Example usage in a Column Cell Renderer:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Cell: props => {
|
|
||||||
const {
|
|
||||||
value
|
|
||||||
columnProps: { rest: { showRowSubComponent } },
|
|
||||||
nestingPath
|
|
||||||
} = props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
onClick={e => showRowSubComponent(nestingPath, e)}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example usage in the ReactTable SubComponent (toggle itself):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const AdvancedExpandReactTable = advancedExpandTableHOC(ReactTable);
|
|
||||||
|
|
||||||
<AdvancedExpandReactTable>
|
|
||||||
...
|
|
||||||
SubComponent={({ row, nestingPath, toggleRowSubComponent }) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
onClick={e => toggleRowSubComponent({ nestingPath }, e)}
|
|
||||||
>
|
|
||||||
{row.value}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
Each Column Renderer (E.g. Cell ) gets the expand functions in its props and each SubComponent gets the expand functions in its props
|
|
||||||
|
|
||||||
Expand functions takes the `nestingPath` or `rowInfo` given to each
|
|
||||||
Column Renderer and SubComponent already by ReactTable.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
@ -1,199 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import set from 'lodash.set'
|
|
||||||
import get from 'lodash.get'
|
|
||||||
|
|
||||||
/*
|
|
||||||
AvancedExpandTableHOC for ReactTable
|
|
||||||
|
|
||||||
HOC which allows any Cell in the row to toggle the row's
|
|
||||||
SubComponent. Also allows the SubComponent to toggle itself.
|
|
||||||
|
|
||||||
Expand functions available to any SubComponent or Column Cell:
|
|
||||||
toggleRowSubComponent
|
|
||||||
showRowSubComponent
|
|
||||||
hideRowSubComponent
|
|
||||||
|
|
||||||
Each Column Renderer (E.g. Cell ) gets the expand functions in its props
|
|
||||||
And Each SubComponent gets the expand functions in its props
|
|
||||||
|
|
||||||
Expand functions takes the `rowInfo` given to each
|
|
||||||
Column Renderer and SubComponent already by ReactTable.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const subComponentWithToggle = (SubComponent, expandFuncs) => props => (
|
|
||||||
<SubComponent {...props} {...expandFuncs} />
|
|
||||||
)
|
|
||||||
|
|
||||||
// each cell in the column gets passed the function to toggle a sub component
|
|
||||||
export const columnsWithToggle = (columns, expandFuncs) =>
|
|
||||||
columns.map(column => {
|
|
||||||
if (column.columns) {
|
|
||||||
return {
|
|
||||||
...column,
|
|
||||||
columns: columnsWithToggle(column.columns, expandFuncs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...column,
|
|
||||||
getProps () {
|
|
||||||
return {
|
|
||||||
...expandFuncs,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const advancedExpandTableHOC = TableComponent =>
|
|
||||||
class AdvancedExpandTable extends Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
this.state = {
|
|
||||||
expanded: {},
|
|
||||||
}
|
|
||||||
this.toggleRowSubComponent = this.toggleRowSubComponent.bind(this)
|
|
||||||
this.showRowSubComponent = this.showRowSubComponent.bind(this)
|
|
||||||
this.hideRowSubComponent = this.hideRowSubComponent.bind(this)
|
|
||||||
this.getTdProps = this.getTdProps.bind(this)
|
|
||||||
this.fireOnExpandedChange = this.fireOnExpandedChange.bind(this)
|
|
||||||
this.expandFuncs = {
|
|
||||||
toggleRowSubComponent: this.toggleRowSubComponent,
|
|
||||||
showRowSubComponent: this.showRowSubComponent,
|
|
||||||
hideRowSubComponent: this.hideRowSubComponent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// after initial render if we get new
|
|
||||||
// data, columns, page changes, etc.
|
|
||||||
// we reset expanded state.
|
|
||||||
componentWillReceiveProps () {
|
|
||||||
this.setState({
|
|
||||||
expanded: {},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
columns: PropTypes.array.isRequired,
|
|
||||||
SubComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element])
|
|
||||||
.isRequired,
|
|
||||||
onExpandedChange: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
onExpandedChange: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
static DisplayName = 'AdvancedExpandTable';
|
|
||||||
|
|
||||||
// since we pass the expand functions to each Cell,
|
|
||||||
// we need to filter it out from being passed as an
|
|
||||||
// actual DOM attribute. See getProps in columnsWithToggle above.
|
|
||||||
static TdComponent ({
|
|
||||||
toggleRowSubComponent,
|
|
||||||
showRowSubComponent,
|
|
||||||
hideRowSubComponent,
|
|
||||||
...rest
|
|
||||||
}) {
|
|
||||||
return <TableComponent.defaultProps.TdComponent {...rest} />
|
|
||||||
}
|
|
||||||
|
|
||||||
fireOnExpandedChange (rowInfo, e) {
|
|
||||||
// fire callback once state has changed.
|
|
||||||
if (this.props.onExpandedChange) {
|
|
||||||
this.props.onExpandedChange(rowInfo, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveNewTableState (rowInfoOrNestingPath, e, expandType) {
|
|
||||||
// derive nestingPath if only rowInfo is passed
|
|
||||||
let nestingPath = rowInfoOrNestingPath
|
|
||||||
|
|
||||||
if (rowInfoOrNestingPath.nestingPath) {
|
|
||||||
nestingPath = rowInfoOrNestingPath.nestingPath
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(
|
|
||||||
prevState => {
|
|
||||||
const isExpanded = get(prevState.expanded, nestingPath)
|
|
||||||
// since we do not support nested rows, a shallow clone is okay.
|
|
||||||
const newExpanded = { ...prevState.expanded }
|
|
||||||
|
|
||||||
switch (expandType) {
|
|
||||||
case 'show':
|
|
||||||
set(newExpanded, nestingPath, {})
|
|
||||||
break
|
|
||||||
case 'hide':
|
|
||||||
set(newExpanded, nestingPath, false)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
// toggle
|
|
||||||
set(newExpanded, nestingPath, isExpanded ? false : {})
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
expanded: newExpanded,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => this.fireOnExpandedChange(rowInfoOrNestingPath, e)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleRowSubComponent (rowInfo, e) {
|
|
||||||
this.resolveNewTableState(rowInfo, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
showRowSubComponent (rowInfo, e) {
|
|
||||||
this.resolveNewTableState(rowInfo, e, 'show')
|
|
||||||
}
|
|
||||||
|
|
||||||
hideRowSubComponent (rowInfo, e) {
|
|
||||||
this.resolveNewTableState(rowInfo, e, 'hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
getTdProps (tableState, rowInfo, column) {
|
|
||||||
const { expander } = column
|
|
||||||
|
|
||||||
if (!expander) {
|
|
||||||
// no overrides
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
// only override onClick for column Td
|
|
||||||
onClick: e => {
|
|
||||||
this.toggleRowSubComponent(rowInfo, e)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getWrappedInstance () {
|
|
||||||
if (!this.wrappedInstance) { console.warn('AdvancedExpandTable - No wrapped instance') }
|
|
||||||
if (this.wrappedInstance.getWrappedInstance) {
|
|
||||||
return this.wrappedInstance.getWrappedInstance()
|
|
||||||
}
|
|
||||||
return this.wrappedInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const {
|
|
||||||
columns, SubComponent, onExpandedChange, ...rest
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
const wrappedColumns = columnsWithToggle(columns, this.expandFuncs)
|
|
||||||
const WrappedSubComponent = subComponentWithToggle(
|
|
||||||
SubComponent,
|
|
||||||
this.expandFuncs
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableComponent
|
|
||||||
{...rest}
|
|
||||||
columns={wrappedColumns}
|
|
||||||
expanded={this.state.expanded}
|
|
||||||
getTdProps={this.getTdProps}
|
|
||||||
SubComponent={WrappedSubComponent}
|
|
||||||
TdComponent={AdvancedExpandTable.TdComponent}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,230 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
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 => ({ resized: newProps.resized }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onResizedChange = resized => {
|
|
||||||
const { onResizedChange } = this.props
|
|
||||||
if (onResizedChange) onResizedChange(resized)
|
|
||||||
else {
|
|
||||||
this.setState(p => ({ 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()
|
|
||||||
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
|
|
||||||
|
|
||||||
const newFold = Object.assign({}, folded)
|
|
||||||
newFold[id] = !newFold[id]
|
|
||||||
|
|
||||||
// Remove the Resized if have
|
|
||||||
this.removeResized(col)
|
|
||||||
|
|
||||||
if (onFoldChange) onFoldChange(newFold)
|
|
||||||
else {
|
|
||||||
this.setState(previous => ({ 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 =>
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
Before Width: | Height: | Size: 583 B |
@ -1 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
Before Width: | Height: | Size: 585 B |
@ -1,119 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
const defaultSelectInputComponent = props => {
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
type={props.selectType || 'checkbox'}
|
|
||||||
aria-label={`${props.checked ? 'Un-select':'Select'} row with id:${props.id}` }
|
|
||||||
checked={props.checked}
|
|
||||||
id={props.id}
|
|
||||||
onClick={e => {
|
|
||||||
const { shiftKey } = e
|
|
||||||
e.stopPropagation()
|
|
||||||
props.onClick(props.id, shiftKey, props.row)
|
|
||||||
}}
|
|
||||||
onChange={() => {}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (Component, options) => {
|
|
||||||
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,
|
|
||||||
row,
|
|
||||||
id: `select-${row[keyField]}`
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
id: 'select-all'
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
selectWidth,
|
|
||||||
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: selectWidth || 30,
|
|
||||||
filterable: false,
|
|
||||||
sortable: false,
|
|
||||||
resizable: false,
|
|
||||||
style: { textAlign: 'center' },
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = (options !== undefined && options.floatingLeft === true) ? [...originalCols, select] : [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: 'checkbox',
|
|
||||||
SelectInputComponent: defaultSelectInputComponent,
|
|
||||||
SelectAllInputComponent: defaultSelectInputComponent,
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
/* 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}`} role="row" 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) || rest.pivotBy.includes(col.id))) {
|
|
||||||
column = {
|
|
||||||
id: col.id,
|
|
||||||
accessor: col.accessor,
|
|
||||||
width: `${treeTableIndent}px`,
|
|
||||||
show: false,
|
|
||||||
Header: '',
|
|
||||||
Expander: col.Expander,
|
|
||||||
PivotValue: col.PivotValue,
|
|
||||||
Pivot: col.Pivot,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return column
|
|
||||||
}),
|
|
||||||
TrComponent,
|
|
||||||
getTrProps,
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Component {...rest} {...extra} ref={r => (this.wrappedInstance = r)} />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wrapper.displayName = 'RTTreeTable'
|
|
||||||
wrapper.defaultProps = {
|
|
||||||
treeTableIndent: 10,
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
}
|
|
||||||
41
src/hooks/useAccessedRows.js
Executable file
41
src/hooks/useAccessedRows.js
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
export default function useAccessedRows ({
|
||||||
|
debug, data, columns, subRowsKey,
|
||||||
|
}) {
|
||||||
|
return useMemo(
|
||||||
|
() => {
|
||||||
|
if (debug) console.info('getAccessedRows')
|
||||||
|
|
||||||
|
// Access the row's data
|
||||||
|
const accessRow = (data, i, depth = 0) => {
|
||||||
|
// Keep the original reference around
|
||||||
|
const original = data
|
||||||
|
|
||||||
|
// Process any subRows
|
||||||
|
const subRows = data[subRowsKey]
|
||||||
|
? data[subRowsKey].map((d, i) => accessRow(d, i, depth + 1))
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
const row = {
|
||||||
|
original,
|
||||||
|
index: i,
|
||||||
|
subRows,
|
||||||
|
depth,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the cells and values
|
||||||
|
row.values = {}
|
||||||
|
columns.forEach(column => {
|
||||||
|
row.values[column.id] = column.accessor ? column.accessor(data) : undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the resolved data
|
||||||
|
return data.map((d, i) => accessRow(d, i))
|
||||||
|
},
|
||||||
|
[data, columns]
|
||||||
|
)
|
||||||
|
}
|
||||||
201
src/hooks/useColumns.js
Executable file
201
src/hooks/useColumns.js
Executable file
@ -0,0 +1,201 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
import { getFirstDefined, getBy } from '../utils'
|
||||||
|
|
||||||
|
export default function useColumns ({
|
||||||
|
debug,
|
||||||
|
groupBy,
|
||||||
|
userColumns,
|
||||||
|
disableSorting,
|
||||||
|
disableGrouping,
|
||||||
|
disableFilters,
|
||||||
|
}) {
|
||||||
|
return useMemo(
|
||||||
|
() => {
|
||||||
|
if (debug) console.info('getColumns')
|
||||||
|
|
||||||
|
// Decorate All the columns
|
||||||
|
const columnTree = decorateColumnTree(userColumns)
|
||||||
|
|
||||||
|
// Get the flat list of all columns
|
||||||
|
let columns = flattenBy(columnTree, 'columns')
|
||||||
|
|
||||||
|
columns = [
|
||||||
|
...groupBy.map(g => columns.find(col => col.id === g)),
|
||||||
|
...columns.filter(col => !groupBy.includes(col.id)),
|
||||||
|
]
|
||||||
|
|
||||||
|
// Get headerGroups
|
||||||
|
const headerGroups = makeHeaderGroups(columns, findMaxDepth(columnTree))
|
||||||
|
const headers = flattenBy(headerGroups, 'headers')
|
||||||
|
|
||||||
|
return {
|
||||||
|
columns,
|
||||||
|
headerGroups,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[groupBy, userColumns]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the depth of the columns
|
||||||
|
function findMaxDepth (columns, depth = 0) {
|
||||||
|
return columns.reduce((prev, curr) => {
|
||||||
|
if (curr.columns) {
|
||||||
|
return Math.max(prev, findMaxDepth(curr.columns, depth + 1))
|
||||||
|
}
|
||||||
|
return depth
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function decorateColumn (column, parent) {
|
||||||
|
// First check for string accessor
|
||||||
|
let {
|
||||||
|
id, accessor, Header, canSortBy, canGroupBy, canFilter,
|
||||||
|
} = column
|
||||||
|
|
||||||
|
if (typeof accessor === 'string') {
|
||||||
|
id = id || accessor
|
||||||
|
const accessorString = accessor
|
||||||
|
accessor = row => getBy(row, accessorString)
|
||||||
|
}
|
||||||
|
|
||||||
|
const grouped = groupBy.includes(id)
|
||||||
|
|
||||||
|
canSortBy = accessor
|
||||||
|
? getFirstDefined(canSortBy, disableSorting === true ? false : undefined, true)
|
||||||
|
: false
|
||||||
|
canGroupBy = accessor
|
||||||
|
? getFirstDefined(canGroupBy, disableGrouping === true ? false : undefined, true)
|
||||||
|
: false
|
||||||
|
canFilter = accessor
|
||||||
|
? getFirstDefined(canFilter, disableFilters === true ? false : undefined, true)
|
||||||
|
: false
|
||||||
|
|
||||||
|
if (!id && typeof Header === 'string') {
|
||||||
|
id = Header
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
// Accessor, but no column id? This is bad.
|
||||||
|
console.error(column)
|
||||||
|
throw new Error('A column id is required!')
|
||||||
|
}
|
||||||
|
|
||||||
|
column = {
|
||||||
|
Header: '',
|
||||||
|
Cell: cell => cell.value,
|
||||||
|
show: true,
|
||||||
|
...column,
|
||||||
|
grouped,
|
||||||
|
canSortBy,
|
||||||
|
canGroupBy,
|
||||||
|
canFilter,
|
||||||
|
id,
|
||||||
|
accessor,
|
||||||
|
parent,
|
||||||
|
}
|
||||||
|
|
||||||
|
column.Aggregated = column.Aggregated || column.Cell
|
||||||
|
|
||||||
|
return column
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the visible columns, headers and flat column list
|
||||||
|
function decorateColumnTree (columns, parent, depth = 0) {
|
||||||
|
return columns.map(column => {
|
||||||
|
column = decorateColumn(column, parent)
|
||||||
|
if (column.columns) {
|
||||||
|
column.columns = decorateColumnTree(column.columns, column, depth + 1)
|
||||||
|
}
|
||||||
|
return column
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function flattenBy (columns, childKey) {
|
||||||
|
const flatColumns = []
|
||||||
|
|
||||||
|
const recurse = columns => {
|
||||||
|
columns.forEach(d => {
|
||||||
|
if (!d[childKey]) {
|
||||||
|
flatColumns.push(d)
|
||||||
|
} else {
|
||||||
|
recurse(d[childKey])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
recurse(columns)
|
||||||
|
|
||||||
|
return flatColumns
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the header groups from the bottom up
|
||||||
|
function makeHeaderGroups (columns, maxDepth) {
|
||||||
|
const headerGroups = []
|
||||||
|
|
||||||
|
const removeChildColumns = column => {
|
||||||
|
delete column.columns
|
||||||
|
if (column.parent) {
|
||||||
|
removeChildColumns(column.parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
columns.forEach(removeChildColumns)
|
||||||
|
|
||||||
|
const buildGroup = (columns, depth = 0) => {
|
||||||
|
const headerGroup = {
|
||||||
|
headers: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentColumns = []
|
||||||
|
|
||||||
|
const hasParents = columns.some(col => col.parent)
|
||||||
|
|
||||||
|
columns.forEach(column => {
|
||||||
|
const isFirst = !parentColumns.length
|
||||||
|
let latestParentColumn = [...parentColumns].reverse()[0]
|
||||||
|
|
||||||
|
// If the column has a parent, add it if necessary
|
||||||
|
if (column.parent) {
|
||||||
|
if (isFirst || latestParentColumn.originalID !== column.parent.id) {
|
||||||
|
parentColumns.push({
|
||||||
|
...column.parent,
|
||||||
|
originalID: column.parent.id,
|
||||||
|
id: [column.parent.id, parentColumns.length].join('_'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (hasParents) {
|
||||||
|
// If other columns have parents, add a place holder if necessary
|
||||||
|
const placeholderColumn = decorateColumn({
|
||||||
|
originalID: [column.id, 'placeholder', maxDepth - depth].join('_'),
|
||||||
|
id: [column.id, 'placeholder', maxDepth - depth, parentColumns.length].join('_'),
|
||||||
|
})
|
||||||
|
if (isFirst || latestParentColumn.originalID !== placeholderColumn.originalID) {
|
||||||
|
parentColumns.push(placeholderColumn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish the new columns[] relationship on the parent
|
||||||
|
if (column.parent || hasParents) {
|
||||||
|
latestParentColumn = [...parentColumns].reverse()[0]
|
||||||
|
latestParentColumn.columns = latestParentColumn.columns || []
|
||||||
|
if (!latestParentColumn.columns.includes(column)) {
|
||||||
|
latestParentColumn.columns.push(column)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headerGroup.headers.push(column)
|
||||||
|
})
|
||||||
|
|
||||||
|
headerGroups.push(headerGroup)
|
||||||
|
|
||||||
|
if (parentColumns.length) {
|
||||||
|
buildGroup(parentColumns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildGroup(columns)
|
||||||
|
|
||||||
|
return headerGroups.reverse()
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/hooks/useControlledState.js
Executable file
24
src/hooks/useControlledState.js
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
const mergeStateAndProps = (state, defaults, props) => {
|
||||||
|
Object.keys(props).forEach(key => {
|
||||||
|
if (typeof props[key] !== 'undefined') {
|
||||||
|
state[key] = props[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.keys(defaults).forEach(key => {
|
||||||
|
if (typeof state[key] === 'undefined') {
|
||||||
|
state[key] = defaults[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useControlledState ([userState, userSetState], defaults, props) {
|
||||||
|
const state = mergeStateAndProps(userState, defaults, props)
|
||||||
|
|
||||||
|
const setState = (updater, type) =>
|
||||||
|
userSetState(old => updater(mergeStateAndProps(old, defaults, props)), type)
|
||||||
|
|
||||||
|
return [state, setState]
|
||||||
|
}
|
||||||
55
src/hooks/useExpandedRows.js
Executable file
55
src/hooks/useExpandedRows.js
Executable file
@ -0,0 +1,55 @@
|
|||||||
|
import { useMemo, useEffect } from 'react'
|
||||||
|
|
||||||
|
import { getBy } from '../utils'
|
||||||
|
|
||||||
|
export default function useExpandedRows ({
|
||||||
|
debug,
|
||||||
|
expanded,
|
||||||
|
expandedKey,
|
||||||
|
columns,
|
||||||
|
rows,
|
||||||
|
setState,
|
||||||
|
actions,
|
||||||
|
}) {
|
||||||
|
return useMemo(
|
||||||
|
() => {
|
||||||
|
if (debug) console.info('getExpandedRows')
|
||||||
|
|
||||||
|
const expandedRows = []
|
||||||
|
|
||||||
|
// Here we do some mutation, but it's the last stage in the
|
||||||
|
// immutable process so this is safe
|
||||||
|
const handleRow = (row, index, depth = 0, parentPath = []) => {
|
||||||
|
// Compute some final state for the row
|
||||||
|
const path = [...parentPath, index]
|
||||||
|
|
||||||
|
row.path = path
|
||||||
|
row.depth = depth
|
||||||
|
|
||||||
|
row.isExpanded = (row.original && row.original[expandedKey]) || getBy(expanded, path)
|
||||||
|
|
||||||
|
row.cells = columns.map(column => {
|
||||||
|
const cell = {
|
||||||
|
column,
|
||||||
|
row,
|
||||||
|
state: null,
|
||||||
|
value: row.values[column.id],
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
})
|
||||||
|
|
||||||
|
expandedRows.push(row)
|
||||||
|
|
||||||
|
if (row.isExpanded && row.subRows && row.subRows.length) {
|
||||||
|
row.subRows.forEach((row, i) => handleRow(row, i, depth + 1, path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.forEach((row, i) => handleRow(row, i))
|
||||||
|
|
||||||
|
return expandedRows
|
||||||
|
},
|
||||||
|
[rows, expanded, columns]
|
||||||
|
)
|
||||||
|
}
|
||||||
67
src/hooks/useFilteredRows.js
Executable file
67
src/hooks/useFilteredRows.js
Executable file
@ -0,0 +1,67 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
export default function useFilteredRows ({
|
||||||
|
debug,
|
||||||
|
filters,
|
||||||
|
rows,
|
||||||
|
columns,
|
||||||
|
filterFn,
|
||||||
|
manualFilters,
|
||||||
|
}) {
|
||||||
|
return useMemo(
|
||||||
|
() => {
|
||||||
|
if (manualFilters || !Object.keys(filters).length) {
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) console.info('getFilteredRows')
|
||||||
|
|
||||||
|
// Filters top level and nested rows
|
||||||
|
const filterRows = rows => {
|
||||||
|
let filteredRows = rows
|
||||||
|
|
||||||
|
filteredRows = Object.entries(filters).reduce((filteredSoFar, [columnID, filterValue]) => {
|
||||||
|
// Find the filters column
|
||||||
|
const column = columns.find(d => d.id === columnID)
|
||||||
|
|
||||||
|
// Don't filter hidden columns or columns that have had their filters disabled
|
||||||
|
if (!column || column.filterable === false) {
|
||||||
|
return filteredSoFar
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterMethod = column.filterMethod || filterFn
|
||||||
|
|
||||||
|
// If 'filterAll' is set to true, pass the entire dataset to the filter method
|
||||||
|
if (column.filterAll) {
|
||||||
|
return filterMethod(filteredSoFar, columnID, filterValue, column)
|
||||||
|
}
|
||||||
|
return filteredSoFar.filter(row => filterMethod(row, columnID, filterValue, column))
|
||||||
|
}, rows)
|
||||||
|
|
||||||
|
// Apply the filter to any subRows
|
||||||
|
filteredRows = filteredRows.map(row => {
|
||||||
|
if (!row.subRows) {
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
subRows: filterRows(row.subRows),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// then filter any rows without subcolumns because it would be strange to show
|
||||||
|
filteredRows = filteredRows.filter(row => {
|
||||||
|
if (!row.subRows) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return row.subRows.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return filteredRows
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterRows(rows)
|
||||||
|
},
|
||||||
|
[rows, filters, manualFilters]
|
||||||
|
)
|
||||||
|
}
|
||||||
77
src/hooks/useGroupedRows.js
Executable file
77
src/hooks/useGroupedRows.js
Executable file
@ -0,0 +1,77 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
import * as aggregations from '../aggregations'
|
||||||
|
|
||||||
|
export default function useGroupedRows ({
|
||||||
|
debug,
|
||||||
|
groupBy,
|
||||||
|
rows,
|
||||||
|
columns,
|
||||||
|
groupByFn,
|
||||||
|
manualGroupBy,
|
||||||
|
userAggregations,
|
||||||
|
}) {
|
||||||
|
return useMemo(
|
||||||
|
() => {
|
||||||
|
if (manualGroupBy || !groupBy.length) {
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
if (debug) console.info('getGroupedRows')
|
||||||
|
// Find the columns that can or are aggregating
|
||||||
|
|
||||||
|
// Uses each column to aggregate rows into a single value
|
||||||
|
const aggregateRowsToValues = rows => {
|
||||||
|
const values = {}
|
||||||
|
columns.forEach(column => {
|
||||||
|
const columnValues = rows.map(d => d.values[column.id])
|
||||||
|
const aggregate =
|
||||||
|
userAggregations[column.aggregate] || aggregations[column.aggregate] || column.aggregate
|
||||||
|
if (typeof aggregate === 'function') {
|
||||||
|
values[column.id] = aggregate(columnValues, rows)
|
||||||
|
} else if (aggregate) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid aggregate "${aggregate}" passed to column with ID: "${column.id}"`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
values[column.id] = columnValues[0]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively group the data
|
||||||
|
const groupRecursively = (rows, groupBy, depth = 0) => {
|
||||||
|
// This is the last level, just return the rows
|
||||||
|
if (depth >= groupBy.length) {
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group the rows together for this level
|
||||||
|
const groupedRows = Object.entries(groupByFn(rows, groupBy[depth])).map(
|
||||||
|
([groupByVal, subRows], index) => {
|
||||||
|
// Recurse to sub rows before aggregation
|
||||||
|
subRows = groupRecursively(subRows, groupBy, depth + 1)
|
||||||
|
|
||||||
|
const values = aggregateRowsToValues(subRows)
|
||||||
|
|
||||||
|
const row = {
|
||||||
|
groupByID: groupBy[depth],
|
||||||
|
groupByVal,
|
||||||
|
values,
|
||||||
|
subRows,
|
||||||
|
depth,
|
||||||
|
index,
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return groupedRows
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign the new data
|
||||||
|
return groupRecursively(rows, groupBy)
|
||||||
|
},
|
||||||
|
[rows, groupBy, columns, manualGroupBy]
|
||||||
|
)
|
||||||
|
}
|
||||||
60
src/hooks/useSortedRows.js
Executable file
60
src/hooks/useSortedRows.js
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
export default function useSortedRows ({
|
||||||
|
debug,
|
||||||
|
sortBy,
|
||||||
|
rows,
|
||||||
|
columns,
|
||||||
|
orderByFn,
|
||||||
|
sortByFn,
|
||||||
|
manualSorting,
|
||||||
|
}) {
|
||||||
|
return useMemo(
|
||||||
|
() => {
|
||||||
|
if (manualSorting || !sortBy.length) {
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
if (debug) console.info('getSortedRows')
|
||||||
|
|
||||||
|
const sortMethodsByColumnID = {}
|
||||||
|
|
||||||
|
columns
|
||||||
|
.filter(col => col.sortMethod)
|
||||||
|
.forEach(col => {
|
||||||
|
sortMethodsByColumnID[col.id] = col.sortMethod
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortData = rows => {
|
||||||
|
// Use the orderByFn to compose multiple sortBy's together.
|
||||||
|
// This will also perform a stable sorting using the row index
|
||||||
|
// if needed.
|
||||||
|
const sortedData = orderByFn(
|
||||||
|
rows,
|
||||||
|
sortBy.map(sort => {
|
||||||
|
// Support custom sorting methods for each column
|
||||||
|
const columnSortBy = sortMethodsByColumnID[sort.id]
|
||||||
|
|
||||||
|
// Return the correct sortFn
|
||||||
|
return (a, b) =>
|
||||||
|
(columnSortBy || sortByFn)(a.values[sort.id], b.values[sort.id], sort.desc)
|
||||||
|
}),
|
||||||
|
// Map the directions
|
||||||
|
sortBy.map(d => !d.desc)
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: this should be optimized. Not good to loop again
|
||||||
|
sortedData.forEach(row => {
|
||||||
|
if (!row.subRows) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
row.subRows = sortData(row.subRows)
|
||||||
|
})
|
||||||
|
|
||||||
|
return sortedData
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortData(rows)
|
||||||
|
},
|
||||||
|
[rows, columns, sortBy, manualSorting]
|
||||||
|
)
|
||||||
|
}
|
||||||
156
src/hooks/useTableMethods.js
Executable file
156
src/hooks/useTableMethods.js
Executable file
@ -0,0 +1,156 @@
|
|||||||
|
import { getBy, setBy, getFirstDefined } from '../utils'
|
||||||
|
|
||||||
|
export default function useTableMethods ({
|
||||||
|
debug,
|
||||||
|
setState,
|
||||||
|
actions,
|
||||||
|
groupBy,
|
||||||
|
columns,
|
||||||
|
defaultSortDesc,
|
||||||
|
filters,
|
||||||
|
}) {
|
||||||
|
const toggleExpandedByPath = (path, set) =>
|
||||||
|
setState(old => {
|
||||||
|
const { expanded } = old
|
||||||
|
const existing = getBy(expanded, path)
|
||||||
|
set = getFirstDefined(set, !existing)
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
expanded: setBy(expanded, path, set),
|
||||||
|
}
|
||||||
|
}, actions.toggleExpanded)
|
||||||
|
|
||||||
|
// Updates sorting based on a columnID, desc flag and multi flag
|
||||||
|
const toggleSortByID = (columnID, desc, multi) =>
|
||||||
|
setState(old => {
|
||||||
|
const { sortBy } = old
|
||||||
|
|
||||||
|
// Find the column for this columnID
|
||||||
|
const column = columns.find(d => d.id === columnID)
|
||||||
|
const resolvedDefaultSortDesc = getFirstDefined(column.defaultSortDesc, defaultSortDesc)
|
||||||
|
|
||||||
|
// Find any existing sortBy for this column
|
||||||
|
const existingSortBy = sortBy.find(d => d.id === columnID)
|
||||||
|
const hasDescDefined = typeof desc !== 'undefined' && desc !== null
|
||||||
|
|
||||||
|
let newSortBy = []
|
||||||
|
|
||||||
|
// What should we do with this filter?
|
||||||
|
let action
|
||||||
|
|
||||||
|
if (!multi) {
|
||||||
|
if (sortBy.length <= 1 && existingSortBy) {
|
||||||
|
if (existingSortBy.desc) {
|
||||||
|
action = 'remove'
|
||||||
|
} else {
|
||||||
|
action = 'toggle'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
action = 'replace'
|
||||||
|
}
|
||||||
|
} else if (!existingSortBy) {
|
||||||
|
action = 'add'
|
||||||
|
} else if (hasDescDefined) {
|
||||||
|
action = 'set'
|
||||||
|
} else {
|
||||||
|
action = 'toggle'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'replace') {
|
||||||
|
newSortBy = [
|
||||||
|
{
|
||||||
|
id: columnID,
|
||||||
|
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} else if (action === 'add') {
|
||||||
|
newSortBy = [
|
||||||
|
...sortBy,
|
||||||
|
{
|
||||||
|
id: columnID,
|
||||||
|
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} else if (action === 'set') {
|
||||||
|
newSortBy = sortBy.map(d => {
|
||||||
|
if (d.id === columnID) {
|
||||||
|
return {
|
||||||
|
...d,
|
||||||
|
desc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
})
|
||||||
|
} else if (action === 'toggle') {
|
||||||
|
newSortBy = sortBy.map(d => {
|
||||||
|
if (d.id === columnID) {
|
||||||
|
return {
|
||||||
|
...d,
|
||||||
|
desc: !existingSortBy.desc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
})
|
||||||
|
} else if (action === 'remove') {
|
||||||
|
newSortBy = []
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
sortBy: newSortBy,
|
||||||
|
}
|
||||||
|
}, actions.sortByChange)
|
||||||
|
|
||||||
|
const toggleGroupBy = (id, toggle) =>
|
||||||
|
setState(old => {
|
||||||
|
const resolvedToggle = typeof set !== 'undefined' ? toggle : !groupBy.includes(id)
|
||||||
|
if (resolvedToggle) {
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
groupBy: [...groupBy, id],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
groupBy: groupBy.filter(d => d !== id),
|
||||||
|
}
|
||||||
|
}, actions.toggleGroupBy)
|
||||||
|
|
||||||
|
const setFilter = (id, val) =>
|
||||||
|
setState(old => {
|
||||||
|
if (typeof val === 'undefined') {
|
||||||
|
const { [id]: prev, ...rest } = filters
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
filters: {
|
||||||
|
...rest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
filters: {
|
||||||
|
...filters,
|
||||||
|
[id]: val,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, actions.setFilter)
|
||||||
|
|
||||||
|
const setAllFilters = filters =>
|
||||||
|
setState(
|
||||||
|
old => ({
|
||||||
|
...old,
|
||||||
|
filters,
|
||||||
|
}),
|
||||||
|
actions.setAllFilters
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
toggleExpandedByPath,
|
||||||
|
toggleSortByID,
|
||||||
|
toggleGroupBy,
|
||||||
|
setFilter,
|
||||||
|
setAllFilters,
|
||||||
|
}
|
||||||
|
}
|
||||||
877
src/index.js
Normal file → Executable file
877
src/index.js
Normal file → Executable file
@ -1,868 +1,11 @@
|
|||||||
import React, { Component } from 'react'
|
import useReactTable, { actions as mainActions } from './useReactTable'
|
||||||
import classnames from 'classnames'
|
import usePagination, { actions as paginationActions } from './usePagination'
|
||||||
//
|
import useFlexLayout, { actions as flexLayoutActions } from './useFlexLayout'
|
||||||
import _ from './utils'
|
|
||||||
import Lifecycle from './lifecycle'
|
const actions = {
|
||||||
import Methods from './methods'
|
...mainActions,
|
||||||
import defaultProps from './defaultProps'
|
...paginationActions,
|
||||||
import propTypes from './propTypes'
|
...flexLayoutActions,
|
||||||
|
|
||||||
export const ReactTableDefaults = defaultProps
|
|
||||||
|
|
||||||
export default class ReactTable extends Methods(Lifecycle(Component)) {
|
|
||||||
static propTypes = propTypes
|
|
||||||
static defaultProps = defaultProps
|
|
||||||
|
|
||||||
constructor (props) {
|
|
||||||
super()
|
|
||||||
|
|
||||||
this.getResolvedState = this.getResolvedState.bind(this)
|
|
||||||
this.getDataModel = this.getDataModel.bind(this)
|
|
||||||
this.getSortedData = this.getSortedData.bind(this)
|
|
||||||
this.fireFetchData = this.fireFetchData.bind(this)
|
|
||||||
this.getPropOrState = this.getPropOrState.bind(this)
|
|
||||||
this.getStateOrProp = this.getStateOrProp.bind(this)
|
|
||||||
this.filterData = this.filterData.bind(this)
|
|
||||||
this.sortData = this.sortData.bind(this)
|
|
||||||
this.getMinRows = this.getMinRows.bind(this)
|
|
||||||
this.onPageChange = this.onPageChange.bind(this)
|
|
||||||
this.onPageSizeChange = this.onPageSizeChange.bind(this)
|
|
||||||
this.sortColumn = this.sortColumn.bind(this)
|
|
||||||
this.filterColumn = this.filterColumn.bind(this)
|
|
||||||
this.resizeColumnStart = this.resizeColumnStart.bind(this)
|
|
||||||
this.resizeColumnEnd = this.resizeColumnEnd.bind(this)
|
|
||||||
this.resizeColumnMoving = this.resizeColumnMoving.bind(this)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
page: props.defaultPage,
|
|
||||||
pageSize: props.defaultPageSize,
|
|
||||||
sorted: props.defaultSorted,
|
|
||||||
expanded: props.defaultExpanded,
|
|
||||||
filtered: props.defaultFiltered,
|
|
||||||
resized: props.defaultResized,
|
|
||||||
currentlyResizing: false,
|
|
||||||
skipNextSort: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const resolvedState = this.getResolvedState()
|
|
||||||
const {
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
style,
|
|
||||||
getProps,
|
|
||||||
getTableProps,
|
|
||||||
getTheadGroupProps,
|
|
||||||
getTheadGroupTrProps,
|
|
||||||
getTheadGroupThProps,
|
|
||||||
getTheadProps,
|
|
||||||
getTheadTrProps,
|
|
||||||
getTheadThProps,
|
|
||||||
getTheadFilterProps,
|
|
||||||
getTheadFilterTrProps,
|
|
||||||
getTheadFilterThProps,
|
|
||||||
getTbodyProps,
|
|
||||||
getTrGroupProps,
|
|
||||||
getTrProps,
|
|
||||||
getTdProps,
|
|
||||||
getTfootProps,
|
|
||||||
getTfootTrProps,
|
|
||||||
getTfootTdProps,
|
|
||||||
getPaginationProps,
|
|
||||||
getLoadingProps,
|
|
||||||
getNoDataProps,
|
|
||||||
getResizerProps,
|
|
||||||
showPagination,
|
|
||||||
showPaginationTop,
|
|
||||||
showPaginationBottom,
|
|
||||||
manual,
|
|
||||||
loadingText,
|
|
||||||
noDataText,
|
|
||||||
sortable,
|
|
||||||
multiSort,
|
|
||||||
resizable,
|
|
||||||
filterable,
|
|
||||||
// Pivoting State
|
|
||||||
pivotIDKey,
|
|
||||||
pivotValKey,
|
|
||||||
pivotBy,
|
|
||||||
subRowsKey,
|
|
||||||
aggregatedKey,
|
|
||||||
originalKey,
|
|
||||||
indexKey,
|
|
||||||
groupedByPivotKey,
|
|
||||||
// State
|
|
||||||
loading,
|
|
||||||
pageSize,
|
|
||||||
page,
|
|
||||||
sorted,
|
|
||||||
filtered,
|
|
||||||
resized,
|
|
||||||
expanded,
|
|
||||||
pages,
|
|
||||||
onExpandedChange,
|
|
||||||
// Components
|
|
||||||
TableComponent,
|
|
||||||
TheadComponent,
|
|
||||||
TbodyComponent,
|
|
||||||
TrGroupComponent,
|
|
||||||
TrComponent,
|
|
||||||
ThComponent,
|
|
||||||
TdComponent,
|
|
||||||
TfootComponent,
|
|
||||||
PaginationComponent,
|
|
||||||
LoadingComponent,
|
|
||||||
SubComponent,
|
|
||||||
NoDataComponent,
|
|
||||||
ResizerComponent,
|
|
||||||
ExpanderComponent,
|
|
||||||
PivotValueComponent,
|
|
||||||
PivotComponent,
|
|
||||||
AggregatedComponent,
|
|
||||||
FilterComponent,
|
|
||||||
PadRowComponent,
|
|
||||||
// Data model
|
|
||||||
resolvedData,
|
|
||||||
visibleColumns,
|
|
||||||
headerGroups,
|
|
||||||
hasHeaderGroups,
|
|
||||||
// Sorted Data
|
|
||||||
sortedData,
|
|
||||||
currentlyResizing,
|
|
||||||
} = resolvedState
|
|
||||||
|
|
||||||
// Pagination
|
|
||||||
const startRow = pageSize * page
|
|
||||||
const endRow = startRow + pageSize
|
|
||||||
let pageRows = manual ? resolvedData : sortedData.slice(startRow, endRow)
|
|
||||||
const minRows = this.getMinRows()
|
|
||||||
const padRows = _.range(Math.max(minRows - pageRows.length, 0))
|
|
||||||
|
|
||||||
const hasColumnFooter = visibleColumns.some(d => d.Footer)
|
|
||||||
const hasFilters = filterable || visibleColumns.some(d => d.filterable)
|
|
||||||
|
|
||||||
const recurseRowsViewIndex = (rows, path = [], index = -1) => [
|
|
||||||
rows.map((row, i) => {
|
|
||||||
index += 1
|
|
||||||
const rowWithViewIndex = {
|
|
||||||
...row,
|
|
||||||
_viewIndex: index,
|
|
||||||
}
|
|
||||||
const newPath = path.concat([i])
|
|
||||||
if (rowWithViewIndex[subRowsKey] && _.get(expanded, newPath)) {
|
|
||||||
[rowWithViewIndex[subRowsKey], index] = recurseRowsViewIndex(
|
|
||||||
rowWithViewIndex[subRowsKey],
|
|
||||||
newPath,
|
|
||||||
index
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return rowWithViewIndex
|
|
||||||
}),
|
|
||||||
index,
|
|
||||||
];
|
|
||||||
[pageRows] = recurseRowsViewIndex(pageRows)
|
|
||||||
|
|
||||||
const canPrevious = page > 0
|
|
||||||
const canNext = page + 1 < pages
|
|
||||||
|
|
||||||
const rowMinWidth = _.sum(
|
|
||||||
visibleColumns.map(d => {
|
|
||||||
const resizedColumn = resized.find(x => x.id === d.id) || {}
|
|
||||||
return _.getFirstDefined(resizedColumn.value, d.width, d.minWidth)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
let rowIndex = -1
|
|
||||||
|
|
||||||
const finalState = {
|
|
||||||
...resolvedState,
|
|
||||||
startRow,
|
|
||||||
endRow,
|
|
||||||
pageRows,
|
|
||||||
minRows,
|
|
||||||
padRows,
|
|
||||||
hasColumnFooter,
|
|
||||||
canPrevious,
|
|
||||||
canNext,
|
|
||||||
rowMinWidth,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 loadingProps = getLoadingProps(finalState, undefined, undefined, this)
|
|
||||||
const noDataProps = getNoDataProps(finalState, undefined, undefined, this)
|
|
||||||
|
|
||||||
// Visual Components
|
|
||||||
|
|
||||||
const makeHeaderGroup = (column, i) => {
|
|
||||||
const resizedValue = col => (resized.find(x => x.id === col.id) || {}).value
|
|
||||||
|
|
||||||
const leafColumns = _.leaves(column, 'columns')
|
|
||||||
const flex = _.sum(
|
|
||||||
leafColumns.map(col => (col.width || resizedValue(col) ? 0 : col.minWidth))
|
|
||||||
)
|
|
||||||
const width = _.sum(
|
|
||||||
leafColumns.map(col => _.getFirstDefined(resizedValue(col), col.width, col.minWidth))
|
|
||||||
)
|
|
||||||
const maxWidth = _.sum(
|
|
||||||
leafColumns.map(col => _.getFirstDefined(resizedValue(col), col.width, col.maxWidth))
|
|
||||||
)
|
|
||||||
|
|
||||||
const theadGroupThProps = _.splitProps(
|
|
||||||
getTheadGroupThProps(finalState, undefined, column, this)
|
|
||||||
)
|
|
||||||
const columnHeaderProps = _.splitProps(
|
|
||||||
column.getHeaderProps(finalState, undefined, column, this)
|
|
||||||
)
|
|
||||||
|
|
||||||
const classes = [
|
|
||||||
column.headerClassName,
|
|
||||||
theadGroupThProps.className,
|
|
||||||
columnHeaderProps.className,
|
|
||||||
]
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
...column.headerStyle,
|
|
||||||
...theadGroupThProps.style,
|
|
||||||
...columnHeaderProps.style,
|
|
||||||
}
|
|
||||||
|
|
||||||
const rest = {
|
|
||||||
...theadGroupThProps.rest,
|
|
||||||
...columnHeaderProps.rest,
|
|
||||||
}
|
|
||||||
|
|
||||||
const flexStyles = {
|
|
||||||
flex: `${flex} 0 auto`,
|
|
||||||
width: _.asPx(width),
|
|
||||||
maxWidth: _.asPx(maxWidth),
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThComponent
|
|
||||||
key={`${i}-${column.id}`}
|
|
||||||
className={classnames(classes)}
|
|
||||||
style={{
|
|
||||||
...styles,
|
|
||||||
...flexStyles,
|
|
||||||
}}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{_.normalizeComponent(column.Header, {
|
|
||||||
data: sortedData,
|
|
||||||
column,
|
|
||||||
})}
|
|
||||||
</ThComponent>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeHeaderGroups = (row, i) => {
|
|
||||||
const theadGroupProps = _.splitProps(getTheadGroupProps(finalState, undefined, row, this))
|
|
||||||
const theadGroupTrProps = _.splitProps(getTheadGroupTrProps(finalState, undefined, row, this))
|
|
||||||
return (
|
|
||||||
<TheadComponent
|
|
||||||
key={`${i}-${row.id}`}
|
|
||||||
className={classnames('-headerGroups', theadGroupProps.className)}
|
|
||||||
style={{
|
|
||||||
...theadGroupProps.style,
|
|
||||||
minWidth: `${rowMinWidth}px`,
|
|
||||||
}}
|
|
||||||
{...theadGroupProps.rest}
|
|
||||||
>
|
|
||||||
<TrComponent
|
|
||||||
className={theadGroupTrProps.className}
|
|
||||||
style={theadGroupTrProps.style}
|
|
||||||
{...theadGroupTrProps.rest}
|
|
||||||
>
|
|
||||||
{row.map(makeHeaderGroup)}
|
|
||||||
</TrComponent>
|
|
||||||
</TheadComponent>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeHeader = (column, i) => {
|
|
||||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
|
||||||
const sort = sorted.find(d => d.id === column.id)
|
|
||||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
|
||||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
|
||||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
|
||||||
const theadThProps = _.splitProps(getTheadThProps(finalState, undefined, column, this))
|
|
||||||
const columnHeaderProps = _.splitProps(
|
|
||||||
column.getHeaderProps(finalState, undefined, column, this)
|
|
||||||
)
|
|
||||||
|
|
||||||
const classes = [column.headerClassName, theadThProps.className, columnHeaderProps.className]
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
...column.headerStyle,
|
|
||||||
...theadThProps.style,
|
|
||||||
...columnHeaderProps.style,
|
|
||||||
}
|
|
||||||
|
|
||||||
const rest = {
|
|
||||||
...theadThProps.rest,
|
|
||||||
...columnHeaderProps.rest,
|
|
||||||
}
|
|
||||||
|
|
||||||
const isResizable = _.getFirstDefined(column.resizable, resizable, false)
|
|
||||||
const resizer = isResizable ? (
|
|
||||||
<ResizerComponent
|
|
||||||
onMouseDown={e => this.resizeColumnStart(e, column, false)}
|
|
||||||
onTouchStart={e => this.resizeColumnStart(e, column, true)}
|
|
||||||
{...getResizerProps('finalState', undefined, column, this)}
|
|
||||||
/>
|
|
||||||
) : null
|
|
||||||
|
|
||||||
const isSortable = _.getFirstDefined(column.sortable, sortable, false)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThComponent
|
|
||||||
key={`${i}-${column.id}`}
|
|
||||||
className={classnames(
|
|
||||||
classes,
|
|
||||||
isResizable && 'rt-resizable-header',
|
|
||||||
sort ? (sort.desc ? '-sort-desc' : '-sort-asc') : '',
|
|
||||||
isSortable && '-cursor-pointer',
|
|
||||||
!show && '-hidden',
|
|
||||||
pivotBy && pivotBy.slice(0, -1).includes(column.id) && 'rt-header-pivot'
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
...styles,
|
|
||||||
flex: `${width} 0 auto`,
|
|
||||||
width: _.asPx(width),
|
|
||||||
maxWidth: _.asPx(maxWidth),
|
|
||||||
}}
|
|
||||||
toggleSort={e => {
|
|
||||||
if (isSortable) this.sortColumn(column, multiSort ? e.shiftKey : false)
|
|
||||||
}}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<div className={classnames(isResizable && 'rt-resizable-header-content')}>
|
|
||||||
{_.normalizeComponent(column.Header, {
|
|
||||||
data: sortedData,
|
|
||||||
column,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
{resizer}
|
|
||||||
</ThComponent>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeHeaders = () => {
|
|
||||||
const theadProps = _.splitProps(getTheadProps(finalState, undefined, undefined, this))
|
|
||||||
const theadTrProps = _.splitProps(getTheadTrProps(finalState, undefined, undefined, this))
|
|
||||||
return (
|
|
||||||
<TheadComponent
|
|
||||||
className={classnames('-header', theadProps.className)}
|
|
||||||
style={{
|
|
||||||
...theadProps.style,
|
|
||||||
minWidth: `${rowMinWidth}px`,
|
|
||||||
}}
|
|
||||||
{...theadProps.rest}
|
|
||||||
>
|
|
||||||
<TrComponent
|
|
||||||
className={theadTrProps.className}
|
|
||||||
style={theadTrProps.style}
|
|
||||||
{...theadTrProps.rest}
|
|
||||||
>
|
|
||||||
{visibleColumns.map(makeHeader)}
|
|
||||||
</TrComponent>
|
|
||||||
</TheadComponent>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeFilter = (column, i) => {
|
|
||||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
|
||||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
|
||||||
const maxWidth = _.getFirstDefined(resizedCol.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,
|
|
||||||
}
|
|
||||||
|
|
||||||
const filter = filtered.find(filter => filter.id === column.id)
|
|
||||||
|
|
||||||
const ResolvedFilterComponent = column.Filter || FilterComponent
|
|
||||||
|
|
||||||
const isFilterable = _.getFirstDefined(column.filterable, filterable, false)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThComponent
|
|
||||||
key={`${i}-${column.id}`}
|
|
||||||
className={classnames(classes)}
|
|
||||||
style={{
|
|
||||||
...styles,
|
|
||||||
flex: `${width} 0 auto`,
|
|
||||||
width: _.asPx(width),
|
|
||||||
maxWidth: _.asPx(maxWidth),
|
|
||||||
}}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{isFilterable
|
|
||||||
? _.normalizeComponent(
|
|
||||||
ResolvedFilterComponent,
|
|
||||||
{
|
|
||||||
column,
|
|
||||||
filter,
|
|
||||||
onChange: value => this.filterColumn(column, value),
|
|
||||||
},
|
|
||||||
defaultProps.column.Filter
|
|
||||||
)
|
|
||||||
: null}
|
|
||||||
</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}
|
|
||||||
>
|
|
||||||
{visibleColumns.map(makeFilter)}
|
|
||||||
</TrComponent>
|
|
||||||
</TheadComponent>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makePageRow = (row, i, path = []) => {
|
|
||||||
const rowInfo = {
|
|
||||||
original: row[originalKey],
|
|
||||||
row,
|
|
||||||
index: row[indexKey],
|
|
||||||
viewIndex: (rowIndex += 1),
|
|
||||||
pageSize,
|
|
||||||
page,
|
|
||||||
level: path.length,
|
|
||||||
nestingPath: path.concat([i]),
|
|
||||||
aggregated: row[aggregatedKey],
|
|
||||||
groupedByPivot: row[groupedByPivotKey],
|
|
||||||
subRows: row[subRowsKey],
|
|
||||||
}
|
|
||||||
const isExpanded = _.get(expanded, rowInfo.nestingPath)
|
|
||||||
const trGroupProps = getTrGroupProps(finalState, rowInfo, undefined, this)
|
|
||||||
const trProps = _.splitProps(getTrProps(finalState, rowInfo, undefined, this))
|
|
||||||
return (
|
|
||||||
<TrGroupComponent key={rowInfo.nestingPath.join('_')} {...trGroupProps}>
|
|
||||||
<TrComponent
|
|
||||||
className={classnames(trProps.className, row._viewIndex % 2 ? '-even' : '-odd')}
|
|
||||||
style={trProps.style}
|
|
||||||
{...trProps.rest}
|
|
||||||
>
|
|
||||||
{visibleColumns.map((column, i2) => {
|
|
||||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
|
||||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
|
||||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
|
||||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
|
||||||
const tdProps = _.splitProps(getTdProps(finalState, rowInfo, column, this))
|
|
||||||
const columnProps = _.splitProps(column.getProps(finalState, rowInfo, column, this))
|
|
||||||
|
|
||||||
const classes = [tdProps.className, column.className, columnProps.className]
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
...tdProps.style,
|
|
||||||
...column.style,
|
|
||||||
...columnProps.style,
|
|
||||||
}
|
|
||||||
|
|
||||||
const cellInfo = {
|
|
||||||
...rowInfo,
|
|
||||||
isExpanded,
|
|
||||||
column: { ...column },
|
|
||||||
value: rowInfo.row[column.id],
|
|
||||||
pivoted: column.pivoted,
|
|
||||||
expander: column.expander,
|
|
||||||
resized,
|
|
||||||
show,
|
|
||||||
width,
|
|
||||||
maxWidth,
|
|
||||||
tdProps,
|
|
||||||
columnProps,
|
|
||||||
classes,
|
|
||||||
styles,
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = cellInfo.value
|
|
||||||
|
|
||||||
let useOnExpanderClick
|
|
||||||
let isBranch
|
|
||||||
let isPreview
|
|
||||||
|
|
||||||
const onExpanderClick = e => {
|
|
||||||
let newExpanded = _.clone(expanded)
|
|
||||||
if (isExpanded) {
|
|
||||||
newExpanded = _.set(newExpanded, cellInfo.nestingPath, false)
|
|
||||||
} else {
|
|
||||||
newExpanded = _.set(newExpanded, cellInfo.nestingPath, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.setStateWithData(
|
|
||||||
{
|
|
||||||
expanded: newExpanded,
|
|
||||||
},
|
|
||||||
() => onExpandedChange && onExpandedChange(newExpanded, cellInfo.nestingPath, e)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to a standard cell
|
|
||||||
let resolvedCell = _.normalizeComponent(column.Cell, cellInfo, value)
|
|
||||||
|
|
||||||
// Resolve Renderers
|
|
||||||
const ResolvedAggregatedComponent =
|
|
||||||
column.Aggregated || (!column.aggregate ? AggregatedComponent : column.Cell)
|
|
||||||
const ResolvedExpanderComponent = column.Expander || ExpanderComponent
|
|
||||||
const ResolvedPivotValueComponent = column.PivotValue || PivotValueComponent
|
|
||||||
const DefaultResolvedPivotComponent =
|
|
||||||
PivotComponent ||
|
|
||||||
(props => (
|
|
||||||
<div>
|
|
||||||
<ResolvedExpanderComponent {...props} />
|
|
||||||
<ResolvedPivotValueComponent {...props} />
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
const ResolvedPivotComponent = column.Pivot || DefaultResolvedPivotComponent
|
|
||||||
|
|
||||||
// Is this cell expandable?
|
|
||||||
if (cellInfo.pivoted || cellInfo.expander) {
|
|
||||||
// Make it expandable by defualt
|
|
||||||
cellInfo.expandable = true
|
|
||||||
useOnExpanderClick = true
|
|
||||||
// If pivoted, has no subRows, and does not have a subComponent,
|
|
||||||
// do not make expandable
|
|
||||||
if (cellInfo.pivoted && !cellInfo.subRows && !SubComponent) {
|
|
||||||
cellInfo.expandable = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cellInfo.pivoted) {
|
|
||||||
// Is this column a branch?
|
|
||||||
isBranch = rowInfo.row[pivotIDKey] === column.id && cellInfo.subRows
|
|
||||||
// Should this column be blank?
|
|
||||||
isPreview =
|
|
||||||
pivotBy.indexOf(column.id) > pivotBy.indexOf(rowInfo.row[pivotIDKey]) &&
|
|
||||||
cellInfo.subRows
|
|
||||||
// Pivot Cell Render Override
|
|
||||||
if (isBranch) {
|
|
||||||
// isPivot
|
|
||||||
resolvedCell = _.normalizeComponent(
|
|
||||||
ResolvedPivotComponent,
|
|
||||||
{
|
|
||||||
...cellInfo,
|
|
||||||
value: row[pivotValKey],
|
|
||||||
},
|
|
||||||
row[pivotValKey]
|
|
||||||
)
|
|
||||||
} else if (isPreview) {
|
|
||||||
// Show the pivot preview
|
|
||||||
resolvedCell = _.normalizeComponent(ResolvedAggregatedComponent, cellInfo, value)
|
|
||||||
} else {
|
|
||||||
resolvedCell = null
|
|
||||||
}
|
|
||||||
} else if (cellInfo.aggregated) {
|
|
||||||
resolvedCell = _.normalizeComponent(ResolvedAggregatedComponent, cellInfo, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cellInfo.expander) {
|
|
||||||
resolvedCell = _.normalizeComponent(
|
|
||||||
ResolvedExpanderComponent,
|
|
||||||
cellInfo,
|
|
||||||
row[pivotValKey]
|
|
||||||
)
|
|
||||||
if (pivotBy) {
|
|
||||||
if (cellInfo.groupedByPivot) {
|
|
||||||
resolvedCell = null
|
|
||||||
}
|
|
||||||
if (!cellInfo.subRows && !SubComponent) {
|
|
||||||
resolvedCell = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvedOnExpanderClick = useOnExpanderClick ? onExpanderClick : () => {}
|
|
||||||
|
|
||||||
// If there are multiple onClick events, make sure they don't
|
|
||||||
// override eachother. This should maybe be expanded to handle all
|
|
||||||
// function attributes
|
|
||||||
const interactionProps = {
|
|
||||||
onClick: resolvedOnExpanderClick,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tdProps.rest.onClick) {
|
|
||||||
interactionProps.onClick = e => {
|
|
||||||
tdProps.rest.onClick(e, () => resolvedOnExpanderClick(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (columnProps.rest.onClick) {
|
|
||||||
interactionProps.onClick = e => {
|
|
||||||
columnProps.rest.onClick(e, () => resolvedOnExpanderClick(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the cell
|
|
||||||
return (
|
|
||||||
<TdComponent
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
|
||||||
key={`${i2}-${column.id}`}
|
|
||||||
className={classnames(
|
|
||||||
classes,
|
|
||||||
!cellInfo.expandable && !show && 'hidden',
|
|
||||||
cellInfo.expandable && 'rt-expandable',
|
|
||||||
(isBranch || isPreview) && 'rt-pivot'
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
...styles,
|
|
||||||
flex: `${width} 0 auto`,
|
|
||||||
width: _.asPx(width),
|
|
||||||
maxWidth: _.asPx(maxWidth),
|
|
||||||
}}
|
|
||||||
{...tdProps.rest}
|
|
||||||
{...columnProps.rest}
|
|
||||||
{...interactionProps}
|
|
||||||
>
|
|
||||||
{resolvedCell}
|
|
||||||
</TdComponent>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</TrComponent>
|
|
||||||
{rowInfo.subRows &&
|
|
||||||
isExpanded &&
|
|
||||||
rowInfo.subRows.map((d, i) => makePageRow(d, i, rowInfo.nestingPath))}
|
|
||||||
{SubComponent &&
|
|
||||||
!rowInfo.subRows &&
|
|
||||||
isExpanded &&
|
|
||||||
SubComponent(rowInfo, () => {
|
|
||||||
const newExpanded = _.clone(expanded)
|
|
||||||
|
|
||||||
_.set(newExpanded, rowInfo.nestingPath, false)
|
|
||||||
})}
|
|
||||||
</TrGroupComponent>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makePadColumn = (column, i) => {
|
|
||||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
|
||||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
|
||||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
|
||||||
const flex = width
|
|
||||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
|
||||||
const tdProps = _.splitProps(getTdProps(finalState, undefined, column, this))
|
|
||||||
const columnProps = _.splitProps(column.getProps(finalState, undefined, column, this))
|
|
||||||
|
|
||||||
const classes = [tdProps.className, column.className, columnProps.className]
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
...tdProps.style,
|
|
||||||
...column.style,
|
|
||||||
...columnProps.style,
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TdComponent
|
|
||||||
key={`${i}-${column.id}`}
|
|
||||||
className={classnames(classes, !show && 'hidden')}
|
|
||||||
style={{
|
|
||||||
...styles,
|
|
||||||
flex: `${flex} 0 auto`,
|
|
||||||
width: _.asPx(width),
|
|
||||||
maxWidth: _.asPx(maxWidth),
|
|
||||||
}}
|
|
||||||
{...tdProps.rest}
|
|
||||||
>
|
|
||||||
{_.normalizeComponent(PadRowComponent)}
|
|
||||||
</TdComponent>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makePadRow = (row, i) => {
|
|
||||||
const trGroupProps = getTrGroupProps(finalState, undefined, undefined, this)
|
|
||||||
const trProps = _.splitProps(getTrProps(finalState, undefined, undefined, this))
|
|
||||||
return (
|
|
||||||
<TrGroupComponent key={`pad-${i}`} {...trGroupProps}>
|
|
||||||
<TrComponent
|
|
||||||
className={classnames(
|
|
||||||
'-padRow',
|
|
||||||
(pageRows.length + i) % 2 ? '-even' : '-odd',
|
|
||||||
trProps.className
|
|
||||||
)}
|
|
||||||
style={trProps.style || {}}
|
|
||||||
>
|
|
||||||
{visibleColumns.map(makePadColumn)}
|
|
||||||
</TrComponent>
|
|
||||||
</TrGroupComponent>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeColumnFooter = (column, i) => {
|
|
||||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
|
||||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
|
||||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
|
||||||
const maxWidth = _.getFirstDefined(resizedCol.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,
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TdComponent
|
|
||||||
key={`${i}-${column.id}`}
|
|
||||||
className={classnames(classes, !show && 'hidden')}
|
|
||||||
style={{
|
|
||||||
...styles,
|
|
||||||
flex: `${width} 0 auto`,
|
|
||||||
width: _.asPx(width),
|
|
||||||
maxWidth: _.asPx(maxWidth),
|
|
||||||
}}
|
|
||||||
{...columnProps.rest}
|
|
||||||
{...tFootTdProps.rest}
|
|
||||||
{...columnFooterProps.rest}
|
|
||||||
>
|
|
||||||
{_.normalizeComponent(column.Footer, {
|
|
||||||
data: sortedData,
|
|
||||||
column,
|
|
||||||
})}
|
|
||||||
</TdComponent>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeColumnFooters = () => {
|
|
||||||
const tFootProps = _.splitProps(getTfootProps(finalState, undefined, undefined, this))
|
|
||||||
const tFootTrProps = _.splitProps(getTfootTrProps(finalState, undefined, undefined, this))
|
|
||||||
return (
|
|
||||||
<TfootComponent
|
|
||||||
className={tFootProps.className}
|
|
||||||
style={{
|
|
||||||
...tFootProps.style,
|
|
||||||
minWidth: `${rowMinWidth}px`,
|
|
||||||
}}
|
|
||||||
{...tFootProps.rest}
|
|
||||||
>
|
|
||||||
<TrComponent
|
|
||||||
className={classnames(tFootTrProps.className)}
|
|
||||||
style={tFootTrProps.style}
|
|
||||||
{...tFootTrProps.rest}
|
|
||||||
>
|
|
||||||
{visibleColumns.map(makeColumnFooter)}
|
|
||||||
</TrComponent>
|
|
||||||
</TfootComponent>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makePagination = isTop => {
|
|
||||||
const paginationProps = _.splitProps(
|
|
||||||
getPaginationProps(finalState, undefined, undefined, this)
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<PaginationComponent
|
|
||||||
{...resolvedState}
|
|
||||||
pages={pages}
|
|
||||||
canPrevious={canPrevious}
|
|
||||||
canNext={canNext}
|
|
||||||
onPageChange={this.onPageChange}
|
|
||||||
onPageSizeChange={this.onPageSizeChange}
|
|
||||||
className={paginationProps.className}
|
|
||||||
style={paginationProps.style}
|
|
||||||
isTop={isTop}
|
|
||||||
{...paginationProps.rest}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeTable = () => (
|
|
||||||
<div
|
|
||||||
className={classnames('ReactTable', className, rootProps.className)}
|
|
||||||
style={{
|
|
||||||
...style,
|
|
||||||
...rootProps.style,
|
|
||||||
}}
|
|
||||||
{...rootProps.rest}
|
|
||||||
>
|
|
||||||
{showPagination && showPaginationTop ? (
|
|
||||||
<div className="pagination-top">{makePagination(true)}</div>
|
|
||||||
) : null}
|
|
||||||
<TableComponent
|
|
||||||
className={classnames(tableProps.className, currentlyResizing ? 'rt-resizing' : '')}
|
|
||||||
style={tableProps.style}
|
|
||||||
{...tableProps.rest}
|
|
||||||
>
|
|
||||||
{hasHeaderGroups ? headerGroups.map(makeHeaderGroups) : null}
|
|
||||||
{makeHeaders()}
|
|
||||||
{hasFilters ? 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 && showPaginationBottom ? (
|
|
||||||
<div className="pagination-bottom">{makePagination(false)}</div>
|
|
||||||
) : 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { useReactTable, usePagination, useFlexLayout, actions }
|
||||||
|
|||||||
322
src/index.styl
322
src/index.styl
@ -1,322 +0,0 @@
|
|||||||
$easeOutQuad = cubic-bezier(0.250, 0.460, 0.450, 0.940)
|
|
||||||
$easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
|
|
||||||
$expandSize = 7px
|
|
||||||
|
|
||||||
input-select-style()
|
|
||||||
border: 1px solid rgba(0,0,0,0.1)
|
|
||||||
background: white
|
|
||||||
padding: 5px 7px
|
|
||||||
font-size: inherit
|
|
||||||
border-radius: 3px
|
|
||||||
font-weight: normal
|
|
||||||
outline:none
|
|
||||||
|
|
||||||
.ReactTable
|
|
||||||
position:relative
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
border: 1px solid alpha(black, .1)
|
|
||||||
*
|
|
||||||
box-sizing: border-box
|
|
||||||
.rt-table
|
|
||||||
flex: auto 1
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
align-items: stretch
|
|
||||||
width: 100%
|
|
||||||
border-collapse: collapse
|
|
||||||
overflow: auto
|
|
||||||
|
|
||||||
.rt-thead
|
|
||||||
flex: 1 0 auto
|
|
||||||
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)
|
|
||||||
|
|
||||||
input
|
|
||||||
select
|
|
||||||
input-select-style();
|
|
||||||
|
|
||||||
.rt-th
|
|
||||||
border-right: 1px solid alpha(black, 0.02)
|
|
||||||
|
|
||||||
&.-header
|
|
||||||
box-shadow: 0 2px 15px 0px alpha(black, .15)
|
|
||||||
|
|
||||||
.rt-tr
|
|
||||||
text-align:center
|
|
||||||
|
|
||||||
.rt-th
|
|
||||||
.rt-td
|
|
||||||
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
|
|
||||||
&.-sort-asc
|
|
||||||
box-shadow:inset 0 3px 0 0 alpha(black, .6)
|
|
||||||
&.-sort-desc
|
|
||||||
box-shadow:inset 0 -3px 0 0 alpha(black, .6)
|
|
||||||
&.-cursor-pointer
|
|
||||||
cursor: pointer
|
|
||||||
&:last-child
|
|
||||||
border-right: 0
|
|
||||||
|
|
||||||
.rt-th:focus
|
|
||||||
outline: none
|
|
||||||
|
|
||||||
.rt-resizable-header
|
|
||||||
overflow: visible
|
|
||||||
&:last-child
|
|
||||||
overflow: hidden
|
|
||||||
|
|
||||||
.rt-resizable-header-content
|
|
||||||
overflow: hidden
|
|
||||||
text-overflow: ellipsis
|
|
||||||
|
|
||||||
.rt-header-pivot
|
|
||||||
border-right-color: #f7f7f7
|
|
||||||
|
|
||||||
.rt-header-pivot:after, .rt-header-pivot:before
|
|
||||||
left: 100%
|
|
||||||
top: 50%
|
|
||||||
border: solid transparent
|
|
||||||
content: " "
|
|
||||||
height: 0
|
|
||||||
width: 0
|
|
||||||
position: absolute
|
|
||||||
pointer-events: none
|
|
||||||
|
|
||||||
.rt-header-pivot:after
|
|
||||||
border-color: rgba(255, 255, 255, 0)
|
|
||||||
border-left-color: #FFF
|
|
||||||
border-width: 8px
|
|
||||||
margin-top: -8px
|
|
||||||
|
|
||||||
.rt-header-pivot:before
|
|
||||||
border-color: rgba(102, 102, 102, 0)
|
|
||||||
border-left-color: #f7f7f7
|
|
||||||
border-width: 10px
|
|
||||||
margin-top: -10px
|
|
||||||
|
|
||||||
.rt-tbody
|
|
||||||
flex: 99999 1 auto
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
overflow: auto
|
|
||||||
// z-index:0
|
|
||||||
.rt-tr-group
|
|
||||||
border-bottom: solid 1px alpha(black, .05)
|
|
||||||
&:last-child
|
|
||||||
border-bottom: 0
|
|
||||||
.rt-td
|
|
||||||
border-right:1px solid alpha(black, .02)
|
|
||||||
&:last-child
|
|
||||||
border-right:0
|
|
||||||
.rt-expandable
|
|
||||||
cursor: pointer
|
|
||||||
text-overflow: clip
|
|
||||||
.rt-tr-group
|
|
||||||
flex: 1 0 auto
|
|
||||||
display: flex
|
|
||||||
flex-direction: column
|
|
||||||
align-items: stretch
|
|
||||||
.rt-tr
|
|
||||||
flex: 1 0 auto
|
|
||||||
display: inline-flex
|
|
||||||
.rt-th
|
|
||||||
.rt-td
|
|
||||||
flex: 1 0 0px
|
|
||||||
white-space: nowrap
|
|
||||||
text-overflow: ellipsis
|
|
||||||
padding: 7px 5px
|
|
||||||
overflow: hidden
|
|
||||||
transition: .3s ease
|
|
||||||
transition-property: width, min-width, padding, opacity
|
|
||||||
|
|
||||||
&.-hidden
|
|
||||||
width: 0 !important
|
|
||||||
min-width: 0 !important
|
|
||||||
padding: 0 !important
|
|
||||||
border:0 !important
|
|
||||||
opacity: 0 !important
|
|
||||||
|
|
||||||
.rt-expander
|
|
||||||
display: inline-block
|
|
||||||
position:relative
|
|
||||||
margin: 0
|
|
||||||
color: transparent
|
|
||||||
margin: 0 10px
|
|
||||||
&:after
|
|
||||||
content: ''
|
|
||||||
position: absolute
|
|
||||||
width: 0
|
|
||||||
height: 0
|
|
||||||
top:50%
|
|
||||||
left:50%
|
|
||||||
transform: translate(-50%, -50%) rotate(-90deg)
|
|
||||||
border-left: ($expandSize * .72) solid transparent
|
|
||||||
border-right: ($expandSize * .72) solid transparent
|
|
||||||
border-top: $expandSize solid alpha(black, .8)
|
|
||||||
transition: all .3s $easeOutBack
|
|
||||||
cursor: pointer
|
|
||||||
&.-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
|
|
||||||
flex: 1 0 auto
|
|
||||||
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)
|
|
||||||
&.-highlight
|
|
||||||
.rt-tbody
|
|
||||||
.rt-tr:not(.-padRow):hover
|
|
||||||
background: alpha(black, .05)
|
|
||||||
|
|
||||||
.-pagination
|
|
||||||
z-index: 1
|
|
||||||
display:flex
|
|
||||||
justify-content: space-between
|
|
||||||
align-items: stretch
|
|
||||||
flex-wrap: wrap
|
|
||||||
padding: 3px
|
|
||||||
box-shadow: 0 0px 15px 0px alpha(black, .1)
|
|
||||||
border-top: 2px solid alpha(black, .1)
|
|
||||||
|
|
||||||
input
|
|
||||||
select
|
|
||||||
input-select-style()
|
|
||||||
|
|
||||||
.-btn
|
|
||||||
appearance:none
|
|
||||||
display:block
|
|
||||||
width:100%
|
|
||||||
height:100%
|
|
||||||
border: 0
|
|
||||||
border-radius: 3px
|
|
||||||
padding: 6px
|
|
||||||
font-size: 1em
|
|
||||||
color: alpha(black, .6)
|
|
||||||
background: alpha(black, .1)
|
|
||||||
transition: all .1s ease
|
|
||||||
cursor: pointer
|
|
||||||
outline:none
|
|
||||||
|
|
||||||
&[disabled]
|
|
||||||
opacity: .5
|
|
||||||
cursor: default
|
|
||||||
|
|
||||||
&:not([disabled]):hover
|
|
||||||
background: alpha(black, .3)
|
|
||||||
color: white
|
|
||||||
|
|
||||||
.-previous
|
|
||||||
.-next
|
|
||||||
flex: 1
|
|
||||||
text-align: center
|
|
||||||
|
|
||||||
.-center
|
|
||||||
flex: 1.5
|
|
||||||
text-align:center
|
|
||||||
margin-bottom:0
|
|
||||||
display: flex
|
|
||||||
flex-direction: row
|
|
||||||
flex-wrap: wrap
|
|
||||||
align-items: center
|
|
||||||
justify-content: space-around
|
|
||||||
|
|
||||||
.-pageInfo
|
|
||||||
display: inline-block
|
|
||||||
margin: 3px 10px
|
|
||||||
white-space: nowrap
|
|
||||||
|
|
||||||
.-pageJump
|
|
||||||
display:inline-block
|
|
||||||
input
|
|
||||||
width: 70px
|
|
||||||
text-align:center
|
|
||||||
|
|
||||||
.-pageSizeOptions
|
|
||||||
margin: 3px 10px
|
|
||||||
|
|
||||||
.rt-noData
|
|
||||||
display:block
|
|
||||||
position:absolute
|
|
||||||
left:50%
|
|
||||||
top:50%
|
|
||||||
transform: translate(-50%, -50%)
|
|
||||||
background: alpha(white, .8)
|
|
||||||
transition: all .3s ease
|
|
||||||
z-index: 1
|
|
||||||
pointer-events: none
|
|
||||||
padding: 20px
|
|
||||||
color: alpha(black, .5)
|
|
||||||
|
|
||||||
.-loading
|
|
||||||
display:block
|
|
||||||
position:absolute
|
|
||||||
left:0
|
|
||||||
right:0
|
|
||||||
top:0
|
|
||||||
bottom:0
|
|
||||||
background: alpha(white, .8)
|
|
||||||
transition: all .3s ease
|
|
||||||
z-index: -1
|
|
||||||
opacity: 0
|
|
||||||
pointer-events: none
|
|
||||||
|
|
||||||
> div
|
|
||||||
position:absolute
|
|
||||||
display: block
|
|
||||||
text-align:center
|
|
||||||
width:100%
|
|
||||||
top:50%
|
|
||||||
left: 0
|
|
||||||
font-size: 15px
|
|
||||||
color: alpha(black, .6)
|
|
||||||
transform: translateY(-52%)
|
|
||||||
transition: all .3s $easeOutQuad
|
|
||||||
|
|
||||||
&.-active
|
|
||||||
opacity: 1
|
|
||||||
z-index: 2
|
|
||||||
pointer-events: all
|
|
||||||
> div
|
|
||||||
transform: translateY(50%)
|
|
||||||
|
|
||||||
.rt-resizing
|
|
||||||
.rt-th
|
|
||||||
.rt-td
|
|
||||||
transition: none!important
|
|
||||||
cursor: col-resize
|
|
||||||
user-select none
|
|
||||||
128
src/lifecycle.js
128
src/lifecycle.js
@ -1,128 +0,0 @@
|
|||||||
export default Base =>
|
|
||||||
class extends Base {
|
|
||||||
componentWillMount () {
|
|
||||||
this.setStateWithData(this.getDataModel(this.getResolvedState(), true))
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.fireFetchData()
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps, nextState) {
|
|
||||||
const oldState = this.getResolvedState()
|
|
||||||
const newState = this.getResolvedState(nextProps, nextState)
|
|
||||||
|
|
||||||
// Do a deep compare of new and old `defaultOption` and
|
|
||||||
// if they are different reset `option = defaultOption`
|
|
||||||
const defaultableOptions = ['sorted', 'filtered', 'resized', 'expanded']
|
|
||||||
defaultableOptions.forEach(x => {
|
|
||||||
const defaultName = `default${x.charAt(0).toUpperCase() + x.slice(1)}`
|
|
||||||
if (JSON.stringify(oldState[defaultName]) !== JSON.stringify(newState[defaultName])) {
|
|
||||||
newState[x] = newState[defaultName]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// If they change these table options, we need to reset defaults
|
|
||||||
// or else we could get into a state where the user has changed the UI
|
|
||||||
// and then disabled the ability to change it back.
|
|
||||||
// e.g. If `filterable` has changed, set `filtered = defaultFiltered`
|
|
||||||
const resettableOptions = ['sortable', 'filterable', 'resizable']
|
|
||||||
resettableOptions.forEach(x => {
|
|
||||||
if (oldState[x] !== newState[x]) {
|
|
||||||
const baseName = x.replace('able', '')
|
|
||||||
const optionName = `${baseName}ed`
|
|
||||||
const defaultName = `default${optionName.charAt(0).toUpperCase() + optionName.slice(1)}`
|
|
||||||
newState[optionName] = newState[defaultName]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Props that trigger a data update
|
|
||||||
if (
|
|
||||||
oldState.data !== newState.data ||
|
|
||||||
oldState.columns !== newState.columns ||
|
|
||||||
oldState.pivotBy !== newState.pivotBy ||
|
|
||||||
oldState.sorted !== newState.sorted ||
|
|
||||||
oldState.filtered !== newState.filtered
|
|
||||||
) {
|
|
||||||
this.setStateWithData(this.getDataModel(newState, oldState.data !== newState.data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.expanded)
|
|
||||||
for (let i = 0; i < keys.length; i += 1) {
|
|
||||||
if (newResolvedState.expanded[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.sorted !== newResolvedState.sorted ||
|
|
||||||
oldState.filtered !== newResolvedState.filtered ||
|
|
||||||
oldState.showFilters !== newResolvedState.showFilters ||
|
|
||||||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData)
|
|
||||||
) {
|
|
||||||
// Handle collapseOnsortedChange & collapseOnDataChange
|
|
||||||
if (
|
|
||||||
(oldState.sorted !== newResolvedState.sorted && this.props.collapseOnSortingChange) ||
|
|
||||||
oldState.filtered !== newResolvedState.filtered ||
|
|
||||||
oldState.showFilters !== newResolvedState.showFilters ||
|
|
||||||
(oldState.sortedData &&
|
|
||||||
!newResolvedState.frozen &&
|
|
||||||
oldState.resolvedData !== newResolvedState.resolvedData &&
|
|
||||||
this.props.collapseOnDataChange)
|
|
||||||
) {
|
|
||||||
newResolvedState.expanded = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(newResolvedState, this.getSortedData(newResolvedState))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set page to 0 if filters change
|
|
||||||
if (oldState.filtered !== newResolvedState.filtered) {
|
|
||||||
newResolvedState.page = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate pageSize all the time
|
|
||||||
if (newResolvedState.sortedData) {
|
|
||||||
newResolvedState.pages = newResolvedState.manual
|
|
||||||
? newResolvedState.pages
|
|
||||||
: Math.ceil(newResolvedState.sortedData.length / newResolvedState.pageSize)
|
|
||||||
newResolvedState.page = newResolvedState.manual ? newResolvedState.page : Math.max(
|
|
||||||
newResolvedState.page >= newResolvedState.pages
|
|
||||||
? newResolvedState.pages - 1
|
|
||||||
: newResolvedState.page,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.setState(newResolvedState, () => {
|
|
||||||
if (cb) {
|
|
||||||
cb()
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
oldState.page !== newResolvedState.page ||
|
|
||||||
oldState.pageSize !== newResolvedState.pageSize ||
|
|
||||||
oldState.sorted !== newResolvedState.sorted ||
|
|
||||||
oldState.filtered !== newResolvedState.filtered
|
|
||||||
) {
|
|
||||||
this.fireFetchData()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
691
src/methods.js
691
src/methods.js
@ -1,691 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import _ from './utils'
|
|
||||||
|
|
||||||
export default Base =>
|
|
||||||
class extends Base {
|
|
||||||
getResolvedState (props, state) {
|
|
||||||
const resolvedState = {
|
|
||||||
..._.compactObject(this.state),
|
|
||||||
..._.compactObject(this.props),
|
|
||||||
..._.compactObject(state),
|
|
||||||
..._.compactObject(props),
|
|
||||||
}
|
|
||||||
return resolvedState
|
|
||||||
}
|
|
||||||
|
|
||||||
getDataModel (newState, dataChanged) {
|
|
||||||
const {
|
|
||||||
columns,
|
|
||||||
pivotBy = [],
|
|
||||||
data,
|
|
||||||
resolveData,
|
|
||||||
pivotIDKey,
|
|
||||||
pivotValKey,
|
|
||||||
subRowsKey,
|
|
||||||
aggregatedKey,
|
|
||||||
nestingLevelKey,
|
|
||||||
originalKey,
|
|
||||||
indexKey,
|
|
||||||
groupedByPivotKey,
|
|
||||||
SubComponent,
|
|
||||||
} = newState
|
|
||||||
|
|
||||||
// Determine if there are Header Groups
|
|
||||||
const hasHeaderGroups = columns.some(column => column.columns)
|
|
||||||
|
|
||||||
// Find the expander column which could be deep in tree of columns
|
|
||||||
const allColumns = _.iterTree(columns, 'columns')
|
|
||||||
const expanderColumn = _.getFirstDefined(allColumns)
|
|
||||||
|
|
||||||
// If we have SubComponent's we need to make sure we have an expander column
|
|
||||||
const hasSubComponentAndNoExpanderColumn = SubComponent && !expanderColumn
|
|
||||||
const columnsWithExpander = hasSubComponentAndNoExpanderColumn
|
|
||||||
? [{ expander: true }, ...columns]
|
|
||||||
: [...columns]
|
|
||||||
|
|
||||||
const makeDecoratedColumn = (column, parentColumn) => {
|
|
||||||
let dcol
|
|
||||||
if (column.expander) {
|
|
||||||
dcol = {
|
|
||||||
...this.props.column,
|
|
||||||
...this.props.expanderDefaults,
|
|
||||||
...column,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dcol = {
|
|
||||||
...this.props.column,
|
|
||||||
...this.props.column,
|
|
||||||
...column,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure minWidth is not greater than maxWidth if set
|
|
||||||
if (dcol.maxWidth < dcol.minWidth) {
|
|
||||||
dcol.minWidth = dcol.maxWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentColumn) {
|
|
||||||
dcol.parentColumn = parentColumn
|
|
||||||
}
|
|
||||||
|
|
||||||
// First check for string accessor
|
|
||||||
if (typeof dcol.accessor === 'string') {
|
|
||||||
dcol.id = dcol.id || dcol.accessor
|
|
||||||
const accessorString = dcol.accessor
|
|
||||||
dcol.accessor = row => _.get(row, accessorString)
|
|
||||||
return dcol
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to functional accessor (but require an ID)
|
|
||||||
if (dcol.accessor && !dcol.id) {
|
|
||||||
console.warn(dcol)
|
|
||||||
throw new Error(
|
|
||||||
'A column id is required if using a non-string accessor for column above.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to an undefined accessor
|
|
||||||
if (!dcol.accessor) {
|
|
||||||
dcol.accessor = () => undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return dcol
|
|
||||||
}
|
|
||||||
|
|
||||||
const allDecoratedColumns = []
|
|
||||||
|
|
||||||
// Decorate the columns
|
|
||||||
const decorateAndAddToAll = (columns, parentColumn) => columns.map(column => {
|
|
||||||
const decoratedColumn = makeDecoratedColumn(column, parentColumn)
|
|
||||||
if (column.columns) {
|
|
||||||
decoratedColumn.columns = decorateAndAddToAll(column.columns, column)
|
|
||||||
}
|
|
||||||
|
|
||||||
allDecoratedColumns.push(decoratedColumn)
|
|
||||||
return decoratedColumn
|
|
||||||
})
|
|
||||||
|
|
||||||
const decoratedColumns = decorateAndAddToAll(columnsWithExpander)
|
|
||||||
const mapVisibleColumns = columns => columns.map(column => {
|
|
||||||
if (column.columns) {
|
|
||||||
const visibleSubColumns = column.columns.filter(
|
|
||||||
d => (pivotBy.indexOf(d.id) > -1 ? false : _.getFirstDefined(d.show, true))
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
...column,
|
|
||||||
columns: mapVisibleColumns(visibleSubColumns),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return column
|
|
||||||
})
|
|
||||||
|
|
||||||
const filterVisibleColumns = columns => columns.filter(
|
|
||||||
column =>
|
|
||||||
column.columns
|
|
||||||
? column.columns.length
|
|
||||||
: pivotBy.indexOf(column.id) > -1
|
|
||||||
? false
|
|
||||||
: _.getFirstDefined(column.show, true)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Build the full array of visible columns - this is an array that contains all columns that
|
|
||||||
// are not hidden via pivoting
|
|
||||||
const allVisibleColumns = filterVisibleColumns(mapVisibleColumns(decoratedColumns.slice()))
|
|
||||||
|
|
||||||
// Find any custom pivot location
|
|
||||||
const pivotIndex = allVisibleColumns.findIndex(col => col.pivot)
|
|
||||||
|
|
||||||
// Handle Pivot Columns
|
|
||||||
if (pivotBy.length) {
|
|
||||||
// Retrieve the pivot columns in the correct pivot order
|
|
||||||
const pivotColumns = []
|
|
||||||
pivotBy.forEach(pivotID => {
|
|
||||||
const found = allDecoratedColumns.find(d => d.id === pivotID)
|
|
||||||
if (found) {
|
|
||||||
pivotColumns.push(found)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const PivotParentColumn = pivotColumns.reduce(
|
|
||||||
(prev, current) => prev && prev === current.parentColumn && current.parentColumn,
|
|
||||||
pivotColumns[0].parentColumn
|
|
||||||
)
|
|
||||||
|
|
||||||
let PivotGroupHeader = hasHeaderGroups && PivotParentColumn.Header
|
|
||||||
PivotGroupHeader = PivotGroupHeader || (() => <strong>Pivoted</strong>)
|
|
||||||
|
|
||||||
let pivotColumnGroup = {
|
|
||||||
Header: PivotGroupHeader,
|
|
||||||
columns: pivotColumns.map(col => ({
|
|
||||||
...this.props.pivotDefaults,
|
|
||||||
...col,
|
|
||||||
pivoted: true,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place the pivotColumns back into the visibleColumns
|
|
||||||
if (pivotIndex >= 0) {
|
|
||||||
pivotColumnGroup = {
|
|
||||||
...allVisibleColumns[pivotIndex],
|
|
||||||
...pivotColumnGroup,
|
|
||||||
}
|
|
||||||
allVisibleColumns.splice(pivotIndex, 1, pivotColumnGroup)
|
|
||||||
} else {
|
|
||||||
allVisibleColumns.unshift(pivotColumnGroup)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build Visible Columns and Header Groups
|
|
||||||
const allColumnHeaders = []
|
|
||||||
|
|
||||||
const addHeader = column => {
|
|
||||||
let level = 0
|
|
||||||
|
|
||||||
// If this column has children, push them first and add this column to the next level
|
|
||||||
if (column.columns) {
|
|
||||||
const childLevels = column.columns.map(addHeader)
|
|
||||||
level = Math.max(...childLevels) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add spans above columns without parents (orphans) to fill the space above them
|
|
||||||
if (allColumnHeaders.length <= level) allColumnHeaders.push([])
|
|
||||||
if (level > 0) {
|
|
||||||
// The spans need to contain the shifted headers as children. This finds all of the
|
|
||||||
// columns in the lower level between the first child of this column and the last child
|
|
||||||
// of the preceding column (if there is one)
|
|
||||||
const lowerLevel = allColumnHeaders[level - 1]
|
|
||||||
const precedingColumn = _.last(allColumnHeaders[level])
|
|
||||||
|
|
||||||
const indexOfFirstChildInLowerLevel = lowerLevel.indexOf(column.columns[0])
|
|
||||||
const indexAfterLastChildInPrecedingColumn = precedingColumn
|
|
||||||
? lowerLevel.indexOf(_.last(precedingColumn.columns)) + 1
|
|
||||||
: 0
|
|
||||||
|
|
||||||
// If there are ophans, add a span above them
|
|
||||||
const orphans = lowerLevel.slice(
|
|
||||||
indexAfterLastChildInPrecedingColumn,
|
|
||||||
indexOfFirstChildInLowerLevel
|
|
||||||
)
|
|
||||||
|
|
||||||
if (orphans.length) {
|
|
||||||
allColumnHeaders[level].push({
|
|
||||||
...this.props.column,
|
|
||||||
columns: orphans,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allColumnHeaders[level].push(column)
|
|
||||||
|
|
||||||
return level
|
|
||||||
}
|
|
||||||
|
|
||||||
allVisibleColumns.forEach(addHeader)
|
|
||||||
|
|
||||||
// visibleColumns is an array containing column definitions for the bottom row of TH elements
|
|
||||||
const visibleColumns = allColumnHeaders.shift()
|
|
||||||
const headerGroups = allColumnHeaders.reverse()
|
|
||||||
|
|
||||||
// Access the data
|
|
||||||
const accessRow = (d, i, level = 0) => {
|
|
||||||
const row = {
|
|
||||||
[originalKey]: d,
|
|
||||||
[indexKey]: i,
|
|
||||||
[subRowsKey]: d[subRowsKey],
|
|
||||||
[nestingLevelKey]: level,
|
|
||||||
}
|
|
||||||
allDecoratedColumns.forEach(column => {
|
|
||||||
if (column.expander) return
|
|
||||||
row[column.id] = column.accessor(d)
|
|
||||||
})
|
|
||||||
if (row[subRowsKey]) {
|
|
||||||
row[subRowsKey] = row[subRowsKey].map((d, i) => accessRow(d, i, level + 1))
|
|
||||||
}
|
|
||||||
return row
|
|
||||||
}
|
|
||||||
|
|
||||||
// // If the data hasn't changed, just use the cached data
|
|
||||||
let resolvedData = this.resolvedData
|
|
||||||
// If the data has changed, run the data resolver and cache the result
|
|
||||||
if (!this.resolvedData || dataChanged) {
|
|
||||||
resolvedData = resolveData(data)
|
|
||||||
this.resolvedData = resolvedData
|
|
||||||
}
|
|
||||||
// Use the resolved data
|
|
||||||
resolvedData = resolvedData.map((d, i) => accessRow(d, i))
|
|
||||||
|
|
||||||
// TODO: Make it possible to fabricate nested rows without pivoting
|
|
||||||
const aggregatingColumns = visibleColumns.filter(d => !d.expander && d.aggregate)
|
|
||||||
|
|
||||||
// If pivoting, recursively group the data
|
|
||||||
const aggregate = rows => {
|
|
||||||
const aggregationValues = {}
|
|
||||||
aggregatingColumns.forEach(column => {
|
|
||||||
const values = rows.map(d => d[column.id])
|
|
||||||
aggregationValues[column.id] = column.aggregate(values, rows)
|
|
||||||
})
|
|
||||||
return aggregationValues
|
|
||||||
}
|
|
||||||
if (pivotBy.length) {
|
|
||||||
const groupRecursively = (rows, keys, i = 0) => {
|
|
||||||
// This is the last level, just return the rows
|
|
||||||
if (i === keys.length) {
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
// Group the rows together for this level
|
|
||||||
let groupedRows = Object.entries(_.groupBy(rows, keys[i])).map(([key, value]) => ({
|
|
||||||
[pivotIDKey]: keys[i],
|
|
||||||
[pivotValKey]: key,
|
|
||||||
[keys[i]]: key,
|
|
||||||
[subRowsKey]: value,
|
|
||||||
[nestingLevelKey]: i,
|
|
||||||
[groupedByPivotKey]: true,
|
|
||||||
}))
|
|
||||||
// Recurse into the subRows
|
|
||||||
groupedRows = groupedRows.map(rowGroup => {
|
|
||||||
const subRows = groupRecursively(rowGroup[subRowsKey], keys, i + 1)
|
|
||||||
return {
|
|
||||||
...rowGroup,
|
|
||||||
[subRowsKey]: subRows,
|
|
||||||
[aggregatedKey]: true,
|
|
||||||
...aggregate(subRows),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return groupedRows
|
|
||||||
}
|
|
||||||
resolvedData = groupRecursively(resolvedData, pivotBy)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...newState,
|
|
||||||
resolvedData,
|
|
||||||
visibleColumns,
|
|
||||||
headerGroups,
|
|
||||||
allDecoratedColumns,
|
|
||||||
hasHeaderGroups,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSortedData (resolvedState) {
|
|
||||||
const {
|
|
||||||
manual,
|
|
||||||
sorted,
|
|
||||||
filtered,
|
|
||||||
defaultFilterMethod,
|
|
||||||
resolvedData,
|
|
||||||
allDecoratedColumns,
|
|
||||||
} = resolvedState
|
|
||||||
|
|
||||||
const sortMethodsByColumnID = {}
|
|
||||||
|
|
||||||
allDecoratedColumns.filter(col => col.sortMethod).forEach(col => {
|
|
||||||
sortMethodsByColumnID[col.id] = col.sortMethod
|
|
||||||
})
|
|
||||||
|
|
||||||
// Resolve the data from either manual data or sorted data
|
|
||||||
return {
|
|
||||||
sortedData: manual
|
|
||||||
? resolvedData
|
|
||||||
: this.sortData(
|
|
||||||
this.filterData(resolvedData, filtered, defaultFilterMethod, allDecoratedColumns),
|
|
||||||
sorted,
|
|
||||||
sortMethodsByColumnID
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fireFetchData () {
|
|
||||||
// determine the current state, preferring certain state values over props
|
|
||||||
const currentState = {
|
|
||||||
...this.getResolvedState(),
|
|
||||||
page: this.getStateOrProp('page'),
|
|
||||||
pageSize: this.getStateOrProp('pageSize'),
|
|
||||||
filter: this.getStateOrProp('filter'),
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onFetchData(currentState, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
getPropOrState (key) {
|
|
||||||
return _.getFirstDefined(this.props[key], this.state[key])
|
|
||||||
}
|
|
||||||
|
|
||||||
getStateOrProp (key) {
|
|
||||||
return _.getFirstDefined(this.state[key], this.props[key])
|
|
||||||
}
|
|
||||||
|
|
||||||
filterData (data, filtered, defaultFilterMethod, visibleColumns) {
|
|
||||||
let filteredData = data
|
|
||||||
|
|
||||||
if (filtered.length) {
|
|
||||||
filteredData = filtered.reduce((filteredSoFar, nextFilter) => {
|
|
||||||
const column = visibleColumns.find(x => x.id === nextFilter.id)
|
|
||||||
|
|
||||||
// Don't filter hidden columns or columns that have had their filters disabled
|
|
||||||
if (!column || column.filterable === false) {
|
|
||||||
return filteredSoFar
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterMethod = column.filterMethod || defaultFilterMethod
|
|
||||||
|
|
||||||
// If 'filterAll' is set to true, pass the entire dataset to the filter method
|
|
||||||
if (column.filterAll) {
|
|
||||||
return filterMethod(nextFilter, filteredSoFar, column)
|
|
||||||
}
|
|
||||||
return filteredSoFar.filter(row => 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],
|
|
||||||
filtered,
|
|
||||||
defaultFilterMethod,
|
|
||||||
visibleColumns
|
|
||||||
),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(row => {
|
|
||||||
if (!row[this.props.subRowsKey]) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return row[this.props.subRowsKey].length > 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredData
|
|
||||||
}
|
|
||||||
|
|
||||||
sortData (data, sorted, sortMethodsByColumnID = {}) {
|
|
||||||
if (!sorted.length) {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
const sortedData = (this.props.orderByMethod || _.orderBy)(
|
|
||||||
data,
|
|
||||||
sorted.map(sort => {
|
|
||||||
// Support custom sorting methods for each column
|
|
||||||
if (sortMethodsByColumnID[sort.id]) {
|
|
||||||
return (a, b) => sortMethodsByColumnID[sort.id](a[sort.id], b[sort.id], sort.desc)
|
|
||||||
}
|
|
||||||
return (a, b) => this.props.defaultSortMethod(a[sort.id], b[sort.id], sort.desc)
|
|
||||||
}),
|
|
||||||
sorted.map(d => !d.desc),
|
|
||||||
this.props.indexKey
|
|
||||||
)
|
|
||||||
|
|
||||||
sortedData.forEach(row => {
|
|
||||||
if (!row[this.props.subRowsKey]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
row[this.props.subRowsKey] = this.sortData(
|
|
||||||
row[this.props.subRowsKey],
|
|
||||||
sorted,
|
|
||||||
sortMethodsByColumnID
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return sortedData
|
|
||||||
}
|
|
||||||
|
|
||||||
getMinRows () {
|
|
||||||
return _.getFirstDefined(this.props.minRows, this.getStateOrProp('pageSize'))
|
|
||||||
}
|
|
||||||
|
|
||||||
// User actions
|
|
||||||
onPageChange (page) {
|
|
||||||
const { onPageChange, collapseOnPageChange } = this.props
|
|
||||||
|
|
||||||
const newState = { page }
|
|
||||||
if (collapseOnPageChange) {
|
|
||||||
newState.expanded = {}
|
|
||||||
}
|
|
||||||
this.setStateWithData(newState, () => onPageChange && onPageChange(page))
|
|
||||||
}
|
|
||||||
|
|
||||||
onPageSizeChange (newPageSize) {
|
|
||||||
const { onPageSizeChange } = this.props
|
|
||||||
const { pageSize, page } = this.getResolvedState()
|
|
||||||
|
|
||||||
// Normalize the page to display
|
|
||||||
const currentRow = pageSize * page
|
|
||||||
const newPage = Math.floor(currentRow / newPageSize)
|
|
||||||
|
|
||||||
this.setStateWithData(
|
|
||||||
{
|
|
||||||
pageSize: newPageSize,
|
|
||||||
page: newPage,
|
|
||||||
},
|
|
||||||
() => onPageSizeChange && onPageSizeChange(newPageSize, newPage)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
sortColumn (column, additive) {
|
|
||||||
const { sorted, skipNextSort, defaultSortDesc } = this.getResolvedState()
|
|
||||||
|
|
||||||
const firstSortDirection = Object.prototype.hasOwnProperty.call(column, 'defaultSortDesc')
|
|
||||||
? column.defaultSortDesc
|
|
||||||
: defaultSortDesc
|
|
||||||
const secondSortDirection = !firstSortDirection
|
|
||||||
|
|
||||||
// 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 { onSortedChange } = this.props
|
|
||||||
|
|
||||||
let newSorted = _.clone(sorted || []).map(d => {
|
|
||||||
d.desc = _.isSortingDesc(d)
|
|
||||||
return d
|
|
||||||
})
|
|
||||||
if (!_.isArray(column)) {
|
|
||||||
// Single-Sort
|
|
||||||
const existingIndex = newSorted.findIndex(d => d.id === column.id)
|
|
||||||
if (existingIndex > -1) {
|
|
||||||
const existing = newSorted[existingIndex]
|
|
||||||
if (existing.desc === secondSortDirection) {
|
|
||||||
if (additive) {
|
|
||||||
newSorted.splice(existingIndex, 1)
|
|
||||||
} else {
|
|
||||||
existing.desc = firstSortDirection
|
|
||||||
newSorted = [existing]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
existing.desc = secondSortDirection
|
|
||||||
if (!additive) {
|
|
||||||
newSorted = [existing]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (additive) {
|
|
||||||
newSorted.push({
|
|
||||||
id: column.id,
|
|
||||||
desc: firstSortDirection,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
newSorted = [
|
|
||||||
{
|
|
||||||
id: column.id,
|
|
||||||
desc: firstSortDirection,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Multi-Sort
|
|
||||||
const existingIndex = newSorted.findIndex(d => d.id === column[0].id)
|
|
||||||
// Existing Sorted Column
|
|
||||||
if (existingIndex > -1) {
|
|
||||||
const existing = newSorted[existingIndex]
|
|
||||||
if (existing.desc === secondSortDirection) {
|
|
||||||
if (additive) {
|
|
||||||
newSorted.splice(existingIndex, column.length)
|
|
||||||
} else {
|
|
||||||
column.forEach((d, i) => {
|
|
||||||
newSorted[existingIndex + i].desc = firstSortDirection
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
column.forEach((d, i) => {
|
|
||||||
newSorted[existingIndex + i].desc = secondSortDirection
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!additive) {
|
|
||||||
newSorted = newSorted.slice(existingIndex, column.length)
|
|
||||||
}
|
|
||||||
// New Sort Column
|
|
||||||
} else if (additive) {
|
|
||||||
newSorted = newSorted.concat(
|
|
||||||
column.map(d => ({
|
|
||||||
id: d.id,
|
|
||||||
desc: firstSortDirection,
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
newSorted = column.map(d => ({
|
|
||||||
id: d.id,
|
|
||||||
desc: firstSortDirection,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setStateWithData(
|
|
||||||
{
|
|
||||||
page: (!sorted.length && newSorted.length) || !additive ? 0 : this.state.page,
|
|
||||||
sorted: newSorted,
|
|
||||||
},
|
|
||||||
() => onSortedChange && onSortedChange(newSorted, column, additive)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
filterColumn (column, value) {
|
|
||||||
const { filtered } = this.getResolvedState()
|
|
||||||
const { onFilteredChange } = this.props
|
|
||||||
|
|
||||||
// Remove old filter first if it exists
|
|
||||||
const newFiltering = (filtered || []).filter(x => x.id !== column.id)
|
|
||||||
|
|
||||||
if (value !== '') {
|
|
||||||
newFiltering.push({
|
|
||||||
id: column.id,
|
|
||||||
value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setStateWithData(
|
|
||||||
{
|
|
||||||
filtered: newFiltering,
|
|
||||||
},
|
|
||||||
() => onFilteredChange && onFilteredChange(newFiltering, column, value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
resizeColumnStart (event, column, isTouch) {
|
|
||||||
event.stopPropagation()
|
|
||||||
const parentWidth = event.target.parentElement.getBoundingClientRect().width
|
|
||||||
|
|
||||||
let pageX
|
|
||||||
if (isTouch) {
|
|
||||||
pageX = event.changedTouches[0].pageX
|
|
||||||
} else {
|
|
||||||
pageX = event.pageX
|
|
||||||
}
|
|
||||||
|
|
||||||
this.trapEvents = true
|
|
||||||
this.setStateWithData(
|
|
||||||
{
|
|
||||||
currentlyResizing: {
|
|
||||||
id: column.id,
|
|
||||||
startX: pageX,
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
resizeColumnMoving (event) {
|
|
||||||
event.stopPropagation()
|
|
||||||
const { onResizedChange, column } = this.props
|
|
||||||
const { resized, currentlyResizing, columns } = this.getResolvedState()
|
|
||||||
const currentColumn = columns.find(c => c.accessor === currentlyResizing.id)
|
|
||||||
const minResizeWidth = currentColumn ? currentColumn.minResizeWidth : column.minResizeWidth
|
|
||||||
|
|
||||||
// Delete old value
|
|
||||||
const newResized = resized.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
|
|
||||||
}
|
|
||||||
|
|
||||||
const newWidth = Math.max(
|
|
||||||
currentlyResizing.parentWidth + pageX - currentlyResizing.startX,
|
|
||||||
minResizeWidth
|
|
||||||
)
|
|
||||||
|
|
||||||
newResized.push({
|
|
||||||
id: currentlyResizing.id,
|
|
||||||
value: newWidth,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setStateWithData(
|
|
||||||
{
|
|
||||||
resized: newResized,
|
|
||||||
},
|
|
||||||
() => onResizedChange && onResizedChange(newResized, event)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
resizeColumnEnd (event) {
|
|
||||||
event.stopPropagation()
|
|
||||||
const 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,
|
|
||||||
currentlyResizing: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,181 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
|
|
||||||
const defaultButton = props => (
|
|
||||||
<button type="button" {...props} className="-btn">
|
|
||||||
{props.children}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default class ReactTablePagination extends Component {
|
|
||||||
static defaultProps = {
|
|
||||||
PreviousComponent: defaultButton,
|
|
||||||
NextComponent: defaultButton,
|
|
||||||
renderPageJump: ({
|
|
||||||
onChange, value, onBlur, onKeyPress, inputType, pageJumpText,
|
|
||||||
}) => (
|
|
||||||
<div className="-pageJump">
|
|
||||||
<input
|
|
||||||
aria-label={pageJumpText}
|
|
||||||
type={inputType}
|
|
||||||
onChange={onChange}
|
|
||||||
value={value}
|
|
||||||
onBlur={onBlur}
|
|
||||||
onKeyPress={onKeyPress}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
renderCurrentPage: page => <span className="-currentPage">{page + 1}</span>,
|
|
||||||
renderTotalPagesCount: pages => <span className="-totalPages">{pages || 1}</span>,
|
|
||||||
renderPageSizeOptions: ({
|
|
||||||
pageSize,
|
|
||||||
pageSizeOptions,
|
|
||||||
rowsSelectorText,
|
|
||||||
onPageSizeChange,
|
|
||||||
rowsText,
|
|
||||||
}) => (
|
|
||||||
<span className="select-wrap -pageSizeOptions">
|
|
||||||
<select
|
|
||||||
aria-label={rowsSelectorText}
|
|
||||||
onChange={e => onPageSizeChange(Number(e.target.value))}
|
|
||||||
value={pageSize}
|
|
||||||
>
|
|
||||||
{pageSizeOptions.map((option, i) => (
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
|
||||||
<option key={i} value={option}>
|
|
||||||
{`${option} ${rowsText}`}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.getSafePage = this.getSafePage.bind(this)
|
|
||||||
this.changePage = this.changePage.bind(this)
|
|
||||||
this.applyPage = this.applyPage.bind(this)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
page: props.page,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (this.props.page !== nextProps.page) {
|
|
||||||
this.setState({ page: nextProps.page })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSafePage (page) {
|
|
||||||
if (Number.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 })
|
|
||||||
if (this.props.page !== page) {
|
|
||||||
this.props.onPageChange(page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyPage (e) {
|
|
||||||
if (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
const page = this.state.page
|
|
||||||
this.changePage(page === '' ? this.props.page : page)
|
|
||||||
}
|
|
||||||
|
|
||||||
getPageJumpProperties () {
|
|
||||||
return {
|
|
||||||
onKeyPress: e => {
|
|
||||||
if (e.which === 13 || e.keyCode === 13) {
|
|
||||||
this.applyPage()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onBlur: this.applyPage,
|
|
||||||
value: this.state.page === '' ? '' : this.state.page + 1,
|
|
||||||
onChange: e => {
|
|
||||||
const val = e.target.value
|
|
||||||
const page = val - 1
|
|
||||||
if (val === '') {
|
|
||||||
return this.setState({ page: val })
|
|
||||||
}
|
|
||||||
this.setState({ page: this.getSafePage(page) })
|
|
||||||
},
|
|
||||||
inputType: this.state.page === '' ? 'text' : 'number',
|
|
||||||
pageJumpText: this.props.pageJumpText,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const {
|
|
||||||
// Computed
|
|
||||||
pages,
|
|
||||||
// Props
|
|
||||||
page,
|
|
||||||
showPageSizeOptions,
|
|
||||||
pageSizeOptions,
|
|
||||||
pageSize,
|
|
||||||
showPageJump,
|
|
||||||
canPrevious,
|
|
||||||
canNext,
|
|
||||||
onPageSizeChange,
|
|
||||||
className,
|
|
||||||
PreviousComponent,
|
|
||||||
NextComponent,
|
|
||||||
renderPageJump,
|
|
||||||
renderCurrentPage,
|
|
||||||
renderTotalPagesCount,
|
|
||||||
renderPageSizeOptions,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classnames(className, '-pagination')} style={this.props.style}>
|
|
||||||
<div className="-previous">
|
|
||||||
<PreviousComponent
|
|
||||||
onClick={() => {
|
|
||||||
if (!canPrevious) return
|
|
||||||
this.changePage(page - 1)
|
|
||||||
}}
|
|
||||||
disabled={!canPrevious}
|
|
||||||
>
|
|
||||||
{this.props.previousText}
|
|
||||||
</PreviousComponent>
|
|
||||||
</div>
|
|
||||||
<div className="-center">
|
|
||||||
<span className="-pageInfo">
|
|
||||||
{this.props.pageText}{' '}
|
|
||||||
{showPageJump ? renderPageJump(this.getPageJumpProperties()) : renderCurrentPage(page)}{' '}
|
|
||||||
{this.props.ofText} {renderTotalPagesCount(pages)}
|
|
||||||
</span>
|
|
||||||
{showPageSizeOptions &&
|
|
||||||
renderPageSizeOptions({
|
|
||||||
pageSize,
|
|
||||||
rowsSelectorText: this.props.rowsSelectorText,
|
|
||||||
pageSizeOptions,
|
|
||||||
onPageSizeChange,
|
|
||||||
rowsText: this.props.rowsText,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="-next">
|
|
||||||
<NextComponent
|
|
||||||
onClick={() => {
|
|
||||||
if (!canNext) return
|
|
||||||
this.changePage(page + 1)
|
|
||||||
}}
|
|
||||||
disabled={!canNext}
|
|
||||||
>
|
|
||||||
{this.props.nextText}
|
|
||||||
</NextComponent>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
169
src/propTypes.js
169
src/propTypes.js
@ -1,169 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
// General
|
|
||||||
data: PropTypes.any,
|
|
||||||
loading: PropTypes.bool,
|
|
||||||
showPagination: PropTypes.bool,
|
|
||||||
showPaginationTop: PropTypes.bool,
|
|
||||||
showPaginationBottom: PropTypes.bool,
|
|
||||||
showPageSizeOptions: PropTypes.bool,
|
|
||||||
pageSizeOptions: PropTypes.array,
|
|
||||||
defaultPageSize: PropTypes.number,
|
|
||||||
showPageJump: PropTypes.bool,
|
|
||||||
collapseOnSortingChange: PropTypes.bool,
|
|
||||||
collapseOnPageChange: PropTypes.bool,
|
|
||||||
collapseOnDataChange: PropTypes.bool,
|
|
||||||
freezeWhenExpanded: PropTypes.bool,
|
|
||||||
sortable: PropTypes.bool,
|
|
||||||
resizable: PropTypes.bool,
|
|
||||||
filterable: PropTypes.bool,
|
|
||||||
defaultSortDesc: PropTypes.bool,
|
|
||||||
defaultSorted: PropTypes.array,
|
|
||||||
defaultFiltered: PropTypes.array,
|
|
||||||
defaultResized: PropTypes.array,
|
|
||||||
defaultExpanded: PropTypes.object,
|
|
||||||
defaultFilterMethod: PropTypes.func,
|
|
||||||
defaultSortMethod: PropTypes.func,
|
|
||||||
|
|
||||||
// Controlled State Callbacks
|
|
||||||
onPageChange: PropTypes.func,
|
|
||||||
onPageSizeChange: PropTypes.func,
|
|
||||||
onSortedChange: PropTypes.func,
|
|
||||||
onFilteredChange: PropTypes.func,
|
|
||||||
onResizedChange: PropTypes.func,
|
|
||||||
onExpandedChange: PropTypes.func,
|
|
||||||
|
|
||||||
// Pivoting
|
|
||||||
pivotBy: PropTypes.array,
|
|
||||||
|
|
||||||
// Key Constants
|
|
||||||
pivotValKey: PropTypes.string,
|
|
||||||
pivotIDKey: PropTypes.string,
|
|
||||||
subRowsKey: PropTypes.string,
|
|
||||||
aggregatedKey: PropTypes.string,
|
|
||||||
nestingLevelKey: PropTypes.string,
|
|
||||||
originalKey: PropTypes.string,
|
|
||||||
indexKey: PropTypes.string,
|
|
||||||
groupedByPivotKey: PropTypes.string,
|
|
||||||
|
|
||||||
// Server-side Callbacks
|
|
||||||
onFetchData: PropTypes.func,
|
|
||||||
|
|
||||||
// Classes
|
|
||||||
className: PropTypes.string,
|
|
||||||
style: PropTypes.object,
|
|
||||||
|
|
||||||
// Component decorators
|
|
||||||
getProps: PropTypes.func,
|
|
||||||
getTableProps: PropTypes.func,
|
|
||||||
getTheadGroupProps: PropTypes.func,
|
|
||||||
getTheadGroupTrProps: PropTypes.func,
|
|
||||||
getTheadGroupThProps: PropTypes.func,
|
|
||||||
getTheadProps: PropTypes.func,
|
|
||||||
getTheadTrProps: PropTypes.func,
|
|
||||||
getTheadThProps: PropTypes.func,
|
|
||||||
getTheadFilterProps: PropTypes.func,
|
|
||||||
getTheadFilterTrProps: PropTypes.func,
|
|
||||||
getTheadFilterThProps: PropTypes.func,
|
|
||||||
getTbodyProps: PropTypes.func,
|
|
||||||
getTrGroupProps: PropTypes.func,
|
|
||||||
getTrProps: PropTypes.func,
|
|
||||||
getTdProps: PropTypes.func,
|
|
||||||
getTfootProps: PropTypes.func,
|
|
||||||
getTfootTrProps: PropTypes.func,
|
|
||||||
getTfootTdProps: PropTypes.func,
|
|
||||||
getPaginationProps: PropTypes.func,
|
|
||||||
getLoadingProps: PropTypes.func,
|
|
||||||
getNoDataProps: PropTypes.func,
|
|
||||||
getResizerProps: PropTypes.func,
|
|
||||||
|
|
||||||
// Global Column Defaults
|
|
||||||
columns: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
// Renderers
|
|
||||||
Cell: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
|
||||||
Header: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
|
||||||
Footer: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
|
||||||
Aggregated: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
|
||||||
Pivot: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
|
||||||
PivotValue: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
|
||||||
Expander: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
|
||||||
Filter: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
|
|
||||||
|
|
||||||
// All Columns
|
|
||||||
sortable: PropTypes.bool, // use table default
|
|
||||||
resizable: PropTypes.bool, // use table default
|
|
||||||
filterable: PropTypes.bool, // use table default
|
|
||||||
show: PropTypes.bool,
|
|
||||||
minWidth: PropTypes.number,
|
|
||||||
minResizeWidth: PropTypes.number,
|
|
||||||
|
|
||||||
// Cells only
|
|
||||||
className: PropTypes.string,
|
|
||||||
style: PropTypes.object,
|
|
||||||
getProps: PropTypes.func,
|
|
||||||
|
|
||||||
// Pivot only
|
|
||||||
aggregate: PropTypes.func,
|
|
||||||
|
|
||||||
// Headers only
|
|
||||||
headerClassName: PropTypes.string,
|
|
||||||
headerStyle: PropTypes.object,
|
|
||||||
getHeaderProps: PropTypes.func,
|
|
||||||
|
|
||||||
// Footers only
|
|
||||||
footerClassName: PropTypes.string,
|
|
||||||
footerStyle: PropTypes.object,
|
|
||||||
getFooterProps: PropTypes.func,
|
|
||||||
filterMethod: PropTypes.func,
|
|
||||||
filterAll: PropTypes.bool,
|
|
||||||
sortMethod: PropTypes.func,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
|
|
||||||
// Global Expander Column Defaults
|
|
||||||
expanderDefaults: PropTypes.shape({
|
|
||||||
sortable: PropTypes.bool,
|
|
||||||
resizable: PropTypes.bool,
|
|
||||||
filterable: PropTypes.bool,
|
|
||||||
width: PropTypes.number,
|
|
||||||
}),
|
|
||||||
|
|
||||||
pivotDefaults: PropTypes.object,
|
|
||||||
|
|
||||||
// Text
|
|
||||||
previousText: PropTypes.node,
|
|
||||||
nextText: PropTypes.node,
|
|
||||||
loadingText: PropTypes.node,
|
|
||||||
noDataText: PropTypes.node,
|
|
||||||
pageText: PropTypes.node,
|
|
||||||
ofText: PropTypes.node,
|
|
||||||
rowsText: PropTypes.node,
|
|
||||||
pageJumpText: PropTypes.node,
|
|
||||||
rowsSelectorText: PropTypes.node,
|
|
||||||
|
|
||||||
// Components
|
|
||||||
TableComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
TheadComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
TbodyComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
TrGroupComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
TrComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
ThComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
TdComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
TfootComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
FilterComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
ExpanderComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
PivotValueComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
AggregatedComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
// this is a computed default generated using
|
|
||||||
PivotComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
// the ExpanderComponent and PivotValueComponent at run-time in methods.js
|
|
||||||
PaginationComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
PreviousComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
NextComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
LoadingComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
NoDataComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
ResizerComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
PadRowComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
|
||||||
}
|
|
||||||
195
src/useFlexLayout.js
Executable file
195
src/useFlexLayout.js
Executable file
@ -0,0 +1,195 @@
|
|||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import { getFirstDefined, sum } from './utils'
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
defaultFlex: PropTypes.number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actions = {}
|
||||||
|
|
||||||
|
export default function useFlexLayout (api, props = {}) {
|
||||||
|
// Validate props
|
||||||
|
PropTypes.checkPropTypes(propTypes, props, 'property', 'useFlexLayout')
|
||||||
|
|
||||||
|
const {
|
||||||
|
hooks: {
|
||||||
|
getRowProps, getHeaderRowProps, getHeaderProps, getCellProps,
|
||||||
|
},
|
||||||
|
visibleColumns,
|
||||||
|
} = api
|
||||||
|
|
||||||
|
const { defaultFlex = 1 } = props
|
||||||
|
|
||||||
|
const [columnMeasurements, setColumnMeasurements] = useState({})
|
||||||
|
|
||||||
|
const rowStyles = useMemo(
|
||||||
|
() => {
|
||||||
|
let sumWidth = 0
|
||||||
|
visibleColumns.forEach(column => {
|
||||||
|
const { width, minWidth } = getSizesForColumn(
|
||||||
|
column,
|
||||||
|
defaultFlex,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
api
|
||||||
|
)
|
||||||
|
if (width) {
|
||||||
|
sumWidth += width
|
||||||
|
} else if (minWidth) {
|
||||||
|
sumWidth += minWidth
|
||||||
|
} else {
|
||||||
|
sumWidth += defaultFlex
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
minWidth: `${sumWidth}px`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[visibleColumns]
|
||||||
|
)
|
||||||
|
|
||||||
|
getRowProps.push(row => rowStyles)
|
||||||
|
getHeaderRowProps.push(row => rowStyles)
|
||||||
|
|
||||||
|
getHeaderProps.push(column => ({
|
||||||
|
style: {
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
...getStylesForColumn(column, columnMeasurements, defaultFlex, api),
|
||||||
|
},
|
||||||
|
// [refKey]: el => {
|
||||||
|
// renderedCellInfoRef.current[key] = {
|
||||||
|
// column,
|
||||||
|
// el
|
||||||
|
// };
|
||||||
|
// },
|
||||||
|
}))
|
||||||
|
|
||||||
|
getCellProps.push(cell => ({
|
||||||
|
style: {
|
||||||
|
display: 'block',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
...getStylesForColumn(cell.column, columnMeasurements, defaultFlex, undefined, api),
|
||||||
|
},
|
||||||
|
// [refKey]: el => {
|
||||||
|
// renderedCellInfoRef.current[columnPathStr] = {
|
||||||
|
// column,
|
||||||
|
// el
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
rowStyles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStylesForColumn (column, columnMeasurements, defaultFlex, api) {
|
||||||
|
const { flex, width, maxWidth } = getSizesForColumn(column, columnMeasurements, defaultFlex, api)
|
||||||
|
|
||||||
|
return {
|
||||||
|
flex: `${flex} 0 auto`,
|
||||||
|
width: `${width}px`,
|
||||||
|
maxWidth: `${maxWidth}px`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSizesForColumn (
|
||||||
|
{
|
||||||
|
columns, id, width, minWidth, maxWidth,
|
||||||
|
},
|
||||||
|
columnMeasurements,
|
||||||
|
defaultFlex,
|
||||||
|
api
|
||||||
|
) {
|
||||||
|
if (columns) {
|
||||||
|
columns = columns
|
||||||
|
.map(column => getSizesForColumn(column, columnMeasurements, defaultFlex, api))
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
if (!columns.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const flex = sum(columns.map(col => col.flex))
|
||||||
|
const width = sum(columns.map(col => col.width))
|
||||||
|
const maxWidth = sum(columns.map(col => col.maxWidth))
|
||||||
|
|
||||||
|
return {
|
||||||
|
flex,
|
||||||
|
width,
|
||||||
|
maxWidth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
flex: width ? 0 : defaultFlex,
|
||||||
|
width:
|
||||||
|
width === 'auto'
|
||||||
|
? columnMeasurements[id] || defaultFlex
|
||||||
|
: getFirstDefined(width, minWidth, defaultFlex),
|
||||||
|
maxWidth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const resetRefs = () => {
|
||||||
|
// if (debug) console.info("resetRefs");
|
||||||
|
// renderedCellInfoRef.current = {};
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const calculateAutoWidths = () => {
|
||||||
|
// RAF(() => {
|
||||||
|
// const newColumnMeasurements = {};
|
||||||
|
// Object.values(renderedCellInfoRef.current).forEach(({ column, el }) => {
|
||||||
|
// if (!el) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let measurement = 0;
|
||||||
|
|
||||||
|
// const measureChildren = children => {
|
||||||
|
// if (children) {
|
||||||
|
// [].slice.call(children).forEach(child => {
|
||||||
|
// measurement = Math.max(
|
||||||
|
// measurement,
|
||||||
|
// Math.ceil(child.offsetWidth) || 0
|
||||||
|
// );
|
||||||
|
// measureChildren(child.children);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// return measurement;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const parentDims = getElementDimensions(el);
|
||||||
|
// measureChildren(el.children);
|
||||||
|
|
||||||
|
// newColumnMeasurements[column.id] = Math.max(
|
||||||
|
// newColumnMeasurements[column.id] || 0,
|
||||||
|
// measurement + parentDims.paddingLeft + parentDims.paddingRight
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const oldKeys = Object.keys(columnMeasurements);
|
||||||
|
// const newKeys = Object.keys(newColumnMeasurements);
|
||||||
|
|
||||||
|
// const needsUpdate =
|
||||||
|
// oldKeys.length !== newKeys.length ||
|
||||||
|
// oldKeys.some(key => {
|
||||||
|
// return columnMeasurements[key] !== newColumnMeasurements[key];
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (needsUpdate) {
|
||||||
|
// setState(old => {
|
||||||
|
// return {
|
||||||
|
// ...old,
|
||||||
|
// columnMeasurements: newColumnMeasurements
|
||||||
|
// };
|
||||||
|
// }, actions.updateAutoWidth);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// };
|
||||||
155
src/usePagination.js
Executable file
155
src/usePagination.js
Executable file
@ -0,0 +1,155 @@
|
|||||||
|
import { useMemo, useState, useLayoutEffect } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import { warnUnknownProps } from './utils'
|
||||||
|
import useControlledState from './hooks/useControlledState'
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
defaultPageSize: PropTypes.number,
|
||||||
|
defaultPageIndex: PropTypes.number,
|
||||||
|
pageSize: PropTypes.number,
|
||||||
|
pages: PropTypes.number,
|
||||||
|
pageIndex: PropTypes.number,
|
||||||
|
onStateChange: PropTypes.func,
|
||||||
|
stateReducer: PropTypes.func,
|
||||||
|
subRowsKey: PropTypes.string,
|
||||||
|
debug: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
pageChange: '__pageChange__',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function usePagination (
|
||||||
|
{
|
||||||
|
debug: parentDebug, rows, prepareRows, groupedRows, sortedRows, filteredRows,
|
||||||
|
},
|
||||||
|
props = {}
|
||||||
|
) {
|
||||||
|
// Validate props
|
||||||
|
PropTypes.checkPropTypes(propTypes, props, 'property', 'usePagination')
|
||||||
|
|
||||||
|
// Destructure props
|
||||||
|
const {
|
||||||
|
defaultPageSize = 10,
|
||||||
|
defaultPageIndex = 0,
|
||||||
|
pageSize: userPageSize,
|
||||||
|
pageIndex: userPageIndex,
|
||||||
|
state: userState,
|
||||||
|
manualPagination,
|
||||||
|
pageCount: userPageCount,
|
||||||
|
pageOptions: userPageOptions,
|
||||||
|
debug = parentDebug,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
warnUnknownProps(rest)
|
||||||
|
|
||||||
|
const defaultState = useState({})
|
||||||
|
const localState = userState || defaultState
|
||||||
|
|
||||||
|
// Build the controllable state
|
||||||
|
const [{ pageSize, pageIndex }, setState] = useControlledState(
|
||||||
|
localState,
|
||||||
|
{
|
||||||
|
pageSize: defaultPageSize,
|
||||||
|
pageIndex: defaultPageIndex,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pageSize: userPageSize,
|
||||||
|
pageIndex: userPageIndex,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
if (manualPagination) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setState(
|
||||||
|
old => ({
|
||||||
|
...old,
|
||||||
|
pageIndex: 0,
|
||||||
|
}),
|
||||||
|
actions.pageChange
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[groupedRows, sortedRows, filteredRows]
|
||||||
|
)
|
||||||
|
|
||||||
|
const { pages, pageCount } = useMemo(
|
||||||
|
() => {
|
||||||
|
if (manualPagination) {
|
||||||
|
return {
|
||||||
|
pages: [rows],
|
||||||
|
pageCount: userPageCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (debug) console.info('getPages')
|
||||||
|
|
||||||
|
// Create a new pages with the first page ready to go.
|
||||||
|
const pages = rows.length ? [] : [[]]
|
||||||
|
|
||||||
|
// Start the pageIndex and currentPage cursors
|
||||||
|
let cursor = 0
|
||||||
|
while (cursor < rows.length) {
|
||||||
|
const end = cursor + pageSize
|
||||||
|
pages.push(rows.slice(cursor, end))
|
||||||
|
cursor = end
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageCount = pages.length
|
||||||
|
|
||||||
|
return {
|
||||||
|
pages,
|
||||||
|
pageCount,
|
||||||
|
pageOptions,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[rows, pageSize, userPageCount]
|
||||||
|
)
|
||||||
|
|
||||||
|
const pageOptions = [...new Array(pageCount)].map((d, i) => i)
|
||||||
|
const page = manualPagination ? rows : pages[pageIndex] || []
|
||||||
|
const canPreviousPage = pageIndex > 0
|
||||||
|
const canNextPage = pageIndex < pageCount - 1
|
||||||
|
|
||||||
|
const gotoPage = pageIndex => {
|
||||||
|
if (debug) console.info('gotoPage')
|
||||||
|
return setState(old => {
|
||||||
|
if (pageIndex < 0 || pageIndex > pageCount - 1) {
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
pageIndex,
|
||||||
|
}
|
||||||
|
}, actions.pageChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousPage = () => gotoPage(pageIndex - 1)
|
||||||
|
|
||||||
|
const nextPage = () => gotoPage(pageIndex + 1)
|
||||||
|
|
||||||
|
const setPageSize = pageSize => {
|
||||||
|
setState(old => ({
|
||||||
|
...old,
|
||||||
|
pageSize,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareRows(page)
|
||||||
|
|
||||||
|
return {
|
||||||
|
pages,
|
||||||
|
pageIndex,
|
||||||
|
pageOptions,
|
||||||
|
page,
|
||||||
|
canPreviousPage,
|
||||||
|
canNextPage,
|
||||||
|
gotoPage,
|
||||||
|
previousPage,
|
||||||
|
nextPage,
|
||||||
|
setPageSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
470
src/useReactTable.js
Executable file
470
src/useReactTable.js
Executable file
@ -0,0 +1,470 @@
|
|||||||
|
import { useState, useMemo, useRef } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
//
|
||||||
|
import {
|
||||||
|
defaultOrderByFn,
|
||||||
|
defaultSortByFn,
|
||||||
|
defaultGroupByFn,
|
||||||
|
defaultFilterFn,
|
||||||
|
flexRender,
|
||||||
|
findExpandedDepth,
|
||||||
|
applyHooks,
|
||||||
|
mergeProps,
|
||||||
|
warnUnknownProps,
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
// Internal Hooks
|
||||||
|
import useControlledState from './hooks/useControlledState'
|
||||||
|
import useColumns from './hooks/useColumns'
|
||||||
|
import useTableMethods from './hooks/useTableMethods'
|
||||||
|
import useAccessedRows from './hooks/useAccessedRows'
|
||||||
|
import useGroupedRows from './hooks/useGroupedRows'
|
||||||
|
import useFilteredRows from './hooks/useFilteredRows'
|
||||||
|
import useSortedRows from './hooks/useSortedRows'
|
||||||
|
import useExpandedRows from './hooks/useExpandedRows'
|
||||||
|
|
||||||
|
const defaultFlex = 1
|
||||||
|
const renderErr =
|
||||||
|
'You must specify a render "type". This could be "Header", "Filter", or any other custom renderers you have set on your column.'
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
sortByChange: '__sortByChange',
|
||||||
|
updateAutoWidth: '__updateAutoWidth__',
|
||||||
|
toggleGroupBy: '__toggleGroupBy__',
|
||||||
|
toggleExpanded: '__toggleExpanded__',
|
||||||
|
setExpanded: '__setExpanded__',
|
||||||
|
setFilter: '__setFilter__',
|
||||||
|
setAllFilters: '__setAllFilters__',
|
||||||
|
}
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
// General
|
||||||
|
data: PropTypes.any,
|
||||||
|
columns: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
aggregate: PropTypes.func,
|
||||||
|
filterFn: PropTypes.func,
|
||||||
|
filterAll: PropTypes.bool,
|
||||||
|
sortByFn: PropTypes.func,
|
||||||
|
resolvedDefaultSortDesc: PropTypes.bool,
|
||||||
|
canSortBy: PropTypes.bool,
|
||||||
|
canGroupBy: PropTypes.bool,
|
||||||
|
Cell: PropTypes.any,
|
||||||
|
Header: PropTypes.any,
|
||||||
|
Filter: PropTypes.any,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
defaultFilters: PropTypes.array,
|
||||||
|
defaultSortBy: PropTypes.array,
|
||||||
|
defaultGroupBy: PropTypes.array,
|
||||||
|
|
||||||
|
groupBy: PropTypes.array,
|
||||||
|
filters: PropTypes.array,
|
||||||
|
sortBy: PropTypes.array,
|
||||||
|
|
||||||
|
filterFn: PropTypes.func,
|
||||||
|
sortByFn: PropTypes.func,
|
||||||
|
orderByFn: PropTypes.func,
|
||||||
|
groupByFn: PropTypes.func,
|
||||||
|
|
||||||
|
manualGrouping: PropTypes.bool,
|
||||||
|
manualFilters: PropTypes.bool,
|
||||||
|
manualSorting: PropTypes.bool,
|
||||||
|
|
||||||
|
disableGrouping: PropTypes.bool,
|
||||||
|
disableFilters: PropTypes.bool,
|
||||||
|
disableSorting: PropTypes.bool,
|
||||||
|
|
||||||
|
defaultSortDesc: PropTypes.bool,
|
||||||
|
disableMultiSort: PropTypes.bool,
|
||||||
|
subRowsKey: PropTypes.string,
|
||||||
|
expandedKey: PropTypes.string,
|
||||||
|
userAggregations: PropTypes.object,
|
||||||
|
|
||||||
|
debug: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useReactTable (props) {
|
||||||
|
// Validate props
|
||||||
|
PropTypes.checkPropTypes(propTypes, props, 'property', 'useReactTable')
|
||||||
|
|
||||||
|
// Destructure props
|
||||||
|
const {
|
||||||
|
data = [],
|
||||||
|
state: userState,
|
||||||
|
columns: userColumns,
|
||||||
|
sortBy: userSortBy,
|
||||||
|
groupBy: userGroupBy,
|
||||||
|
filters: userFilters,
|
||||||
|
expanded: userExpanded,
|
||||||
|
defaultSortBy = [],
|
||||||
|
defaultGroupBy = [],
|
||||||
|
defaultFilters = {},
|
||||||
|
defaultExpanded = {},
|
||||||
|
manualGrouping,
|
||||||
|
manualFilters,
|
||||||
|
manualSorting,
|
||||||
|
disableSorting,
|
||||||
|
disableGrouping,
|
||||||
|
disableFilters,
|
||||||
|
defaultSortDesc,
|
||||||
|
disableMultiSort,
|
||||||
|
subRowsKey = 'subRows',
|
||||||
|
expandedKey = 'expanded',
|
||||||
|
filterFn = defaultFilterFn,
|
||||||
|
sortByFn = defaultSortByFn,
|
||||||
|
orderByFn = defaultOrderByFn,
|
||||||
|
groupByFn = defaultGroupByFn,
|
||||||
|
userAggregations = {},
|
||||||
|
debug,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
warnUnknownProps(rest)
|
||||||
|
|
||||||
|
const defaultState = useState({})
|
||||||
|
|
||||||
|
const localState = userState || defaultState
|
||||||
|
|
||||||
|
// Build the controllable state
|
||||||
|
const [{
|
||||||
|
sortBy, groupBy, filters, expanded,
|
||||||
|
}, setState] = useControlledState(
|
||||||
|
localState,
|
||||||
|
{
|
||||||
|
sortBy: defaultSortBy,
|
||||||
|
groupBy: defaultGroupBy,
|
||||||
|
filters: defaultFilters,
|
||||||
|
expanded: defaultExpanded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sortBy: userSortBy,
|
||||||
|
groupBy: userGroupBy,
|
||||||
|
filters: userFilters,
|
||||||
|
expanded: userExpanded,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const expandedDepth = findExpandedDepth(expanded)
|
||||||
|
const renderedCellInfoRef = useRef({})
|
||||||
|
|
||||||
|
// Build the base column
|
||||||
|
let { columns, headerGroups, headers } = useColumns({
|
||||||
|
debug,
|
||||||
|
groupBy,
|
||||||
|
userColumns,
|
||||||
|
defaultFlex,
|
||||||
|
renderedCellInfoRef,
|
||||||
|
disableSorting,
|
||||||
|
disableGrouping,
|
||||||
|
disableFilters,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Use data and columns as memoization
|
||||||
|
const accessedRows = useAccessedRows({
|
||||||
|
debug,
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
subRowsKey,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Use rows and groupBy as memoization for row grouping
|
||||||
|
const groupedRows = useGroupedRows({
|
||||||
|
debug,
|
||||||
|
rows: accessedRows,
|
||||||
|
groupBy,
|
||||||
|
columns,
|
||||||
|
groupByFn,
|
||||||
|
manualGrouping,
|
||||||
|
userAggregations,
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredRows = useFilteredRows({
|
||||||
|
debug,
|
||||||
|
rows: groupedRows,
|
||||||
|
filters,
|
||||||
|
columns,
|
||||||
|
filterFn,
|
||||||
|
manualFilters,
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortedRows = useSortedRows({
|
||||||
|
debug,
|
||||||
|
rows: filteredRows,
|
||||||
|
sortBy,
|
||||||
|
columns,
|
||||||
|
orderByFn,
|
||||||
|
sortByFn,
|
||||||
|
manualSorting,
|
||||||
|
})
|
||||||
|
|
||||||
|
const rows = useExpandedRows({
|
||||||
|
debug,
|
||||||
|
rows: sortedRows,
|
||||||
|
expanded,
|
||||||
|
expandedKey,
|
||||||
|
columns,
|
||||||
|
groupBy,
|
||||||
|
setState,
|
||||||
|
actions,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mutate columns to reflect sorting state
|
||||||
|
columns.forEach(column => {
|
||||||
|
const { id } = column
|
||||||
|
column.sorted = sortBy.find(d => d.id === id)
|
||||||
|
column.sortedIndex = sortBy.findIndex(d => d.id === id)
|
||||||
|
column.sortedDesc = column.sorted ? column.sorted.desc : undefined
|
||||||
|
column.filterVal = filters[id]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
|
||||||
|
const {
|
||||||
|
toggleExpandedByPath,
|
||||||
|
toggleSortByID,
|
||||||
|
toggleGroupBy,
|
||||||
|
setFilter,
|
||||||
|
setAllFilters,
|
||||||
|
} = useTableMethods({
|
||||||
|
debug,
|
||||||
|
setState,
|
||||||
|
actions,
|
||||||
|
groupBy,
|
||||||
|
columns,
|
||||||
|
defaultSortDesc,
|
||||||
|
filters,
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
// State
|
||||||
|
columns,
|
||||||
|
expandedDepth,
|
||||||
|
accessedRows,
|
||||||
|
groupedRows,
|
||||||
|
filteredRows,
|
||||||
|
sortedRows,
|
||||||
|
rows,
|
||||||
|
headerGroups,
|
||||||
|
renderedCellInfoRef,
|
||||||
|
disableMultiSort,
|
||||||
|
|
||||||
|
// Controllable state
|
||||||
|
groupBy,
|
||||||
|
filters,
|
||||||
|
sortBy,
|
||||||
|
expanded,
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
// State manager,
|
||||||
|
...state,
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
toggleExpandedByPath,
|
||||||
|
toggleSortByID,
|
||||||
|
toggleGroupBy,
|
||||||
|
setFilter,
|
||||||
|
setAllFilters,
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.forEach(column => attachApi(column, api))
|
||||||
|
headers.forEach(header => attachApi(header, api))
|
||||||
|
|
||||||
|
const visibleColumns = columns.filter(column => {
|
||||||
|
column.visible = typeof column.show === 'function' ? column.show(state) : !!column.show
|
||||||
|
return column.visible
|
||||||
|
})
|
||||||
|
|
||||||
|
state.visibleColumns = visibleColumns
|
||||||
|
api.visibleColumns = visibleColumns
|
||||||
|
|
||||||
|
api.hooks = {
|
||||||
|
getTableProps: [],
|
||||||
|
getRowProps: [],
|
||||||
|
getHeaderRowProps: [],
|
||||||
|
getCellProps: [],
|
||||||
|
getHeaderProps: [],
|
||||||
|
getSortByToggleProps: [],
|
||||||
|
getGroupByToggleProps: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
api.getTableProps = props => mergeProps(
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
overflowX: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
applyHooks(api.hooks.getTableProps),
|
||||||
|
props
|
||||||
|
)
|
||||||
|
|
||||||
|
api.getRowProps = props => mergeProps(applyHooks(api.hooks.getRowProps), props)
|
||||||
|
|
||||||
|
// Filter out hidden header groups or header groups that
|
||||||
|
// have no visible columns, then add the getRowProps method
|
||||||
|
headerGroups = useMemo(
|
||||||
|
() =>
|
||||||
|
headerGroups.filter((headerGroup, i) => {
|
||||||
|
headerGroup.headers = headerGroup.headers.filter(header => {
|
||||||
|
const recurse = columns => columns.filter(column => {
|
||||||
|
if (column.columns) {
|
||||||
|
return recurse(column.columns)
|
||||||
|
}
|
||||||
|
return column.visible
|
||||||
|
}).length
|
||||||
|
if (header.columns) {
|
||||||
|
return recurse(header.columns)
|
||||||
|
}
|
||||||
|
return header.visible
|
||||||
|
})
|
||||||
|
|
||||||
|
if (headerGroup.headers.length) {
|
||||||
|
headerGroup.getRowProps = (props = {}) => mergeProps(
|
||||||
|
{
|
||||||
|
key: [`header${i}`].join('_'),
|
||||||
|
},
|
||||||
|
applyHooks(api.hooks.getHeaderRowProps, headerGroup),
|
||||||
|
props
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}),
|
||||||
|
[headerGroups]
|
||||||
|
)
|
||||||
|
|
||||||
|
// A function that mutates and prepares page rows with methods
|
||||||
|
api.prepareRows = rows => {
|
||||||
|
rows.forEach((row, i) => {
|
||||||
|
const { path } = row
|
||||||
|
row.getRowProps = props => mergeProps(
|
||||||
|
{ key: ['row', i].join('_') },
|
||||||
|
applyHooks(api.hooks.getRowProps, row),
|
||||||
|
props
|
||||||
|
)
|
||||||
|
|
||||||
|
row.toggleExpanded = set => toggleExpandedByPath(path, set)
|
||||||
|
|
||||||
|
row.cells = row.cells.filter(cell => cell.column.visible);
|
||||||
|
[row.groupByCell, ...row.cells].forEach(cell => {
|
||||||
|
if (!cell) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { column } = cell
|
||||||
|
|
||||||
|
cell.getCellProps = props => {
|
||||||
|
const columnPathStr = [i, column.id].join('_')
|
||||||
|
return mergeProps(
|
||||||
|
{
|
||||||
|
key: ['cell', columnPathStr].join('_'),
|
||||||
|
},
|
||||||
|
applyHooks(api.hooks.getCellProps, cell),
|
||||||
|
props
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.render = (type, userProps = {}) => {
|
||||||
|
if (!type) {
|
||||||
|
throw new Error(
|
||||||
|
'You must specify a render "type". This could be "Cell", "Header", "Filter", "Aggregated" or any other custom renderers you have set on your column.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return flexRender(column[type], {
|
||||||
|
...api,
|
||||||
|
...cell,
|
||||||
|
...userProps,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeHook = api => {
|
||||||
|
api.hook = (hook, ...args) => {
|
||||||
|
const result = hook(api, ...args) || {}
|
||||||
|
const newApi = {
|
||||||
|
...api,
|
||||||
|
...result,
|
||||||
|
}
|
||||||
|
makeHook(newApi)
|
||||||
|
return newApi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeHook(api)
|
||||||
|
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachApi (column, api) {
|
||||||
|
const {
|
||||||
|
id, canSortBy, canGroupBy, canFilter,
|
||||||
|
} = column
|
||||||
|
const { toggleSortByID, toggleGroupBy, setFilter } = api
|
||||||
|
|
||||||
|
column.render = (type, userProps = {}) => {
|
||||||
|
if (!type) {
|
||||||
|
throw new Error(renderErr)
|
||||||
|
}
|
||||||
|
return flexRender(column[type], {
|
||||||
|
...api,
|
||||||
|
...column,
|
||||||
|
...userProps,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canSortBy) {
|
||||||
|
column.toggleSortBy = (desc, multi) => toggleSortByID(id, desc, multi)
|
||||||
|
}
|
||||||
|
if (canGroupBy) {
|
||||||
|
column.toggleGroupBy = () => toggleGroupBy(id)
|
||||||
|
}
|
||||||
|
if (canFilter) {
|
||||||
|
column.setFilter = val => setFilter(id, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
column.getSortByToggleProps = props => mergeProps(
|
||||||
|
{
|
||||||
|
onClick: canSortBy
|
||||||
|
? e => {
|
||||||
|
e.persist()
|
||||||
|
column.toggleSortBy(undefined, !api.disableMultiSort && e.shiftKey)
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
style: {
|
||||||
|
cursor: canSortBy ? 'pointer' : undefined,
|
||||||
|
},
|
||||||
|
title: 'Toggle SortBy',
|
||||||
|
},
|
||||||
|
applyHooks(api.hooks.getSortByToggleProps, column),
|
||||||
|
props
|
||||||
|
)
|
||||||
|
|
||||||
|
column.getGroupByToggleProps = props => mergeProps(
|
||||||
|
{
|
||||||
|
onClick: canGroupBy
|
||||||
|
? e => {
|
||||||
|
e.persist()
|
||||||
|
column.toggleGroupBy()
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
style: {
|
||||||
|
cursor: canGroupBy ? 'pointer' : undefined,
|
||||||
|
},
|
||||||
|
title: 'Toggle GroupBy',
|
||||||
|
},
|
||||||
|
applyHooks(api.hooks.getGroupByToggleProps, column),
|
||||||
|
props
|
||||||
|
)
|
||||||
|
|
||||||
|
column.getHeaderProps = props => mergeProps(
|
||||||
|
{
|
||||||
|
key: ['header', column.id].join('_'),
|
||||||
|
},
|
||||||
|
applyHooks(api.hooks.getHeaderProps, column),
|
||||||
|
props
|
||||||
|
)
|
||||||
|
}
|
||||||
294
src/utils.js
Normal file → Executable file
294
src/utils.js
Normal file → Executable file
@ -1,134 +1,53 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import classnames from 'classnames'
|
|
||||||
//
|
|
||||||
export default {
|
|
||||||
get,
|
|
||||||
set,
|
|
||||||
takeRight,
|
|
||||||
last,
|
|
||||||
orderBy,
|
|
||||||
range,
|
|
||||||
remove,
|
|
||||||
clone,
|
|
||||||
leaves,
|
|
||||||
iterTree,
|
|
||||||
getFirstDefined,
|
|
||||||
sum,
|
|
||||||
makeTemplateComponent,
|
|
||||||
groupBy,
|
|
||||||
isArray,
|
|
||||||
splitProps,
|
|
||||||
compactObject,
|
|
||||||
isSortingDesc,
|
|
||||||
normalizeComponent,
|
|
||||||
asPx,
|
|
||||||
}
|
|
||||||
|
|
||||||
function get (obj, path, def) {
|
export function getBy (obj, path, def) {
|
||||||
if (!path) {
|
if (!path) {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
const pathObj = makePathArray(path)
|
const pathObj = makePathArray(path)
|
||||||
let val
|
let val
|
||||||
try {
|
try {
|
||||||
val = pathObj.reduce((current, pathPart) => current[pathPart], obj)
|
val = pathObj.reduce((cursor, pathPart) => cursor[pathPart], obj)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// continue regardless of error
|
// continue regardless of error
|
||||||
}
|
}
|
||||||
return typeof val !== 'undefined' ? val : def
|
return typeof val !== 'undefined' ? val : def
|
||||||
}
|
}
|
||||||
|
|
||||||
function set (obj = {}, path, value) {
|
export function defaultOrderByFn (arr, funcs, dirs) {
|
||||||
const keys = makePathArray(path)
|
return [...arr].sort((rowA, rowB) => {
|
||||||
let keyPart
|
|
||||||
let cursor = obj
|
|
||||||
while ((keyPart = keys.shift()) && keys.length) {
|
|
||||||
if (!cursor[keyPart]) {
|
|
||||||
cursor[keyPart] = {}
|
|
||||||
}
|
|
||||||
cursor = cursor[keyPart]
|
|
||||||
}
|
|
||||||
cursor[keyPart] = value
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
function takeRight (arr, n) {
|
|
||||||
const start = n > arr.length ? 0 : arr.length - n
|
|
||||||
return arr.slice(start)
|
|
||||||
}
|
|
||||||
|
|
||||||
function last (arr) {
|
|
||||||
return arr[arr.length - 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
function range (n) {
|
|
||||||
const arr = []
|
|
||||||
for (let i = 0; i < n; i += 1) {
|
|
||||||
arr.push(n)
|
|
||||||
}
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
function orderBy (arr, funcs, dirs, indexKey) {
|
|
||||||
return arr.sort((rowA, rowB) => {
|
|
||||||
for (let i = 0; i < funcs.length; i += 1) {
|
for (let i = 0; i < funcs.length; i += 1) {
|
||||||
const comp = funcs[i]
|
const sortFn = funcs[i]
|
||||||
const desc = dirs[i] === false || dirs[i] === 'desc'
|
const desc = dirs[i] === false || dirs[i] === 'desc'
|
||||||
const sortInt = comp(rowA, rowB)
|
const sortInt = sortFn(rowA, rowB)
|
||||||
if (sortInt) {
|
if (sortInt !== 0) {
|
||||||
return desc ? -sortInt : sortInt
|
return desc ? -sortInt : sortInt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Use the row index for tie breakers
|
return dirs[0] ? rowA.index - rowB.index : rowB.index - rowA.index
|
||||||
return dirs[0] ? rowA[indexKey] - rowB[indexKey] : rowB[indexKey] - rowA[indexKey]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove (a, b) {
|
export function defaultSortByFn (a, b, desc) {
|
||||||
return a.filter((o, i) => {
|
// force null and undefined to the bottom
|
||||||
const r = b(o)
|
a = a === null || a === undefined ? '' : a
|
||||||
if (r) {
|
b = b === null || b === undefined ? '' : b
|
||||||
a.splice(i, 1)
|
// force any string values to lowercase
|
||||||
return true
|
a = typeof a === 'string' ? a.toLowerCase() : a
|
||||||
}
|
b = typeof b === 'string' ? b.toLowerCase() : b
|
||||||
return false
|
// Return either 1 or -1 to indicate a sort priority
|
||||||
})
|
if (a > b) {
|
||||||
}
|
return 1
|
||||||
|
|
||||||
function clone (a) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(
|
|
||||||
JSON.stringify(a, (key, value) => {
|
|
||||||
if (typeof value === 'function') {
|
|
||||||
return value.toString()
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} catch (e) {
|
|
||||||
return a
|
|
||||||
}
|
}
|
||||||
}
|
if (a < b) {
|
||||||
|
return -1
|
||||||
function leaves (root, childProperty) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(root, childProperty)) {
|
|
||||||
const children = root[childProperty].map(child => leaves(child, childProperty))
|
|
||||||
return [].concat(...children)
|
|
||||||
}
|
}
|
||||||
|
// returning 0, undefined or any falsey value will defer to the next
|
||||||
return [root]
|
// sorting mechanism or eventually the columns index via the orderByFn
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function iterTree (root, childProperty) {
|
export function getFirstDefined (...args) {
|
||||||
if (Object.prototype.hasOwnProperty.call(root, childProperty)) {
|
|
||||||
const children = root[childProperty].map(child => leaves(child, childProperty))
|
|
||||||
return [].concat(root, ...children)
|
|
||||||
}
|
|
||||||
|
|
||||||
return [root]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFirstDefined (...args) {
|
|
||||||
for (let i = 0; i < args.length; i += 1) {
|
for (let i = 0; i < args.length; i += 1) {
|
||||||
if (typeof args[i] !== 'undefined') {
|
if (typeof args[i] !== 'undefined') {
|
||||||
return args[i]
|
return args[i]
|
||||||
@ -136,44 +55,108 @@ function getFirstDefined (...args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sum (arr) {
|
export function defaultGroupByFn (rows, grouper) {
|
||||||
return arr.reduce((a, b) => a + b, 0)
|
return rows.reduce((prev, row, i) => {
|
||||||
}
|
const resKey = typeof grouper === 'function' ? grouper(row.values, i) : row.values[grouper]
|
||||||
|
prev[resKey] = Array.isArray(prev[resKey]) ? prev[resKey] : []
|
||||||
function makeTemplateComponent (compClass, displayName) {
|
prev[resKey].push(row)
|
||||||
if (!displayName) {
|
return prev
|
||||||
throw new Error('No displayName found for template component:', compClass)
|
|
||||||
}
|
|
||||||
const cmp = ({ children, className, ...rest }) => (
|
|
||||||
<div className={classnames(compClass, className)} {...rest}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
cmp.displayName = displayName
|
|
||||||
return cmp
|
|
||||||
}
|
|
||||||
|
|
||||||
function groupBy (xs, key) {
|
|
||||||
return xs.reduce((rv, x, i) => {
|
|
||||||
const resKey = typeof key === 'function' ? key(x, i) : x[key]
|
|
||||||
rv[resKey] = isArray(rv[resKey]) ? rv[resKey] : []
|
|
||||||
rv[resKey].push(x)
|
|
||||||
return rv
|
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
function asPx (value) {
|
export function defaultFilterFn (row, id, value, column) {
|
||||||
value = Number(value)
|
return row.values[id] !== undefined
|
||||||
return Number.isNaN(value) ? null : `${value}px`
|
? String(row.values[id])
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(String(value).toLowerCase())
|
||||||
|
: true
|
||||||
}
|
}
|
||||||
|
|
||||||
function isArray (a) {
|
export function setBy (obj = {}, path, value) {
|
||||||
return Array.isArray(a)
|
const recurse = (obj, depth = 0) => {
|
||||||
|
const key = path[depth]
|
||||||
|
const target = typeof obj[key] !== 'object' ? {} : obj[key]
|
||||||
|
const subValue = depth === path.length - 1 ? value : recurse(target, depth + 1)
|
||||||
|
return {
|
||||||
|
...obj,
|
||||||
|
[key]: subValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return recurse(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ########################################################################
|
export function getElementDimensions (element) {
|
||||||
// Non-exported Helpers
|
const rect = element.getBoundingClientRect()
|
||||||
// ########################################################################
|
const style = window.getComputedStyle(element)
|
||||||
|
const margins = {
|
||||||
|
left: parseInt(style.marginLeft),
|
||||||
|
right: parseInt(style.marginRight),
|
||||||
|
}
|
||||||
|
const padding = {
|
||||||
|
left: parseInt(style.paddingLeft),
|
||||||
|
right: parseInt(style.paddingRight),
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
left: Math.ceil(rect.left),
|
||||||
|
width: Math.ceil(rect.width),
|
||||||
|
outerWidth: Math.ceil(rect.width + margins.left + margins.right + padding.left + padding.right),
|
||||||
|
marginLeft: margins.left,
|
||||||
|
marginRight: margins.right,
|
||||||
|
paddingLeft: padding.left,
|
||||||
|
paddingRight: padding.right,
|
||||||
|
scrollWidth: element.scrollWidth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flexRender (Comp, props) {
|
||||||
|
if (typeof Comp === 'function') {
|
||||||
|
return Object.getPrototypeOf(Comp).isReactComponent ? <Comp {...props} /> : Comp(props)
|
||||||
|
}
|
||||||
|
return Comp
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findExpandedDepth (obj, depth = 1) {
|
||||||
|
return Object.values(obj).reduce((prev, curr) => {
|
||||||
|
if (typeof curr === 'object') {
|
||||||
|
return Math.max(prev, findExpandedDepth(curr, depth + 1))
|
||||||
|
}
|
||||||
|
return depth
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mergeProps = (...groups) => {
|
||||||
|
let props = {}
|
||||||
|
groups.forEach(({ style = {}, className, ...rest } = {}) => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
...rest,
|
||||||
|
style: {
|
||||||
|
...(props.style || {}),
|
||||||
|
...style,
|
||||||
|
},
|
||||||
|
className: [props.className, className].filter(Boolean).join(' '),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
export const applyHooks = (hooks, ...args) =>
|
||||||
|
hooks.reduce((prev, next) => mergeProps(prev, next(...args)), {})
|
||||||
|
|
||||||
|
export const warnUnknownProps = props => {
|
||||||
|
if (Object.keys(props).length) {
|
||||||
|
throw new Error(
|
||||||
|
`Unknown options passed to useReactTable:
|
||||||
|
|
||||||
|
${JSON.stringify(props, null, 2)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sum (arr) {
|
||||||
|
return arr.reduce((prev, curr) => prev + curr, 0)
|
||||||
|
}
|
||||||
|
|
||||||
function makePathArray (obj) {
|
function makePathArray (obj) {
|
||||||
return flattenDeep(obj)
|
return flattenDeep(obj)
|
||||||
@ -184,7 +167,7 @@ function makePathArray (obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function flattenDeep (arr, newArr = []) {
|
function flattenDeep (arr, newArr = []) {
|
||||||
if (!isArray(arr)) {
|
if (!Array.isArray(arr)) {
|
||||||
newArr.push(arr)
|
newArr.push(arr)
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < arr.length; i += 1) {
|
for (let i = 0; i < arr.length; i += 1) {
|
||||||
@ -193,40 +176,3 @@ function flattenDeep (arr, newArr = []) {
|
|||||||
}
|
}
|
||||||
return newArr
|
return newArr
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitProps ({ className, style, ...rest }) {
|
|
||||||
return {
|
|
||||||
className,
|
|
||||||
style,
|
|
||||||
rest: rest || {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function compactObject (obj) {
|
|
||||||
const newObj = {}
|
|
||||||
if (obj) {
|
|
||||||
Object.keys(obj).map(key => {
|
|
||||||
if (
|
|
||||||
Object.prototype.hasOwnProperty.call(obj, key) &&
|
|
||||||
obj[key] !== undefined &&
|
|
||||||
typeof obj[key] !== 'undefined'
|
|
||||||
) {
|
|
||||||
newObj[key] = obj[key]
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return newObj
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSortingDesc (d) {
|
|
||||||
return !!(d.sort === 'desc' || d.desc === true || d.asc === false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeComponent (Comp, params = {}, fallback = Comp) {
|
|
||||||
return typeof Comp === 'function' ? (
|
|
||||||
<Comp {...params} />
|
|
||||||
) : (
|
|
||||||
fallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user