mirror of
https://github.com/gosticks/react-table.git
synced 2026-08-19 08:20:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fe97f921f | ||
|
|
9a0369ee33 | ||
|
|
48e6d16d58 | ||
|
|
e4208e3b0f | ||
|
|
e6d03673ff | ||
|
|
c3fc84db01 | ||
|
|
3334796223 | ||
|
|
92b70393fa | ||
|
|
efeabe3536 | ||
|
|
f018bac58f | ||
|
|
a8f360a408 | ||
|
|
82f3913034 | ||
|
|
cb3cc05a59 | ||
|
|
e8146b5d14 | ||
|
|
eef59a559e | ||
|
|
3b18e8c8c1 | ||
|
|
dd6b8af304 | ||
|
|
f242d2f3f6 | ||
|
|
9dd0333571 | ||
|
|
085616d858 | ||
|
|
25ef0b2273 | ||
|
|
1fef30311c | ||
|
|
5351f79293 | ||
|
|
0bb3f2421e | ||
|
|
c392c90eb6 | ||
|
|
fc5cc00fce | ||
|
|
a8c8211ef8 | ||
|
|
8bff135119 | ||
|
|
e930d834c6 | ||
|
|
7700f6a1a0 | ||
|
|
7a941b880f | ||
|
|
2e143e8d27 | ||
|
|
ee2345f43c | ||
|
|
e3129a2c80 | ||
|
|
1f943ffe25 | ||
|
|
c854c6914d | ||
|
|
80cef0d4c6 | ||
|
|
302eb3d951 | ||
|
|
4af3637c0b | ||
|
|
4a18536da7 | ||
|
|
850f659309 | ||
|
|
0409463052 | ||
|
|
66afd8cded | ||
|
|
5aa764de03 | ||
|
|
2af8f75de9 | ||
|
|
5615c43b29 |
+3
-1
@@ -1,3 +1,5 @@
|
||||
node_modules/
|
||||
|
||||
lib/
|
||||
react-table.js
|
||||
react-table.css
|
||||
*.log
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
script: npm test
|
||||
@@ -1,32 +1,40 @@
|
||||

|
||||
|
||||
# react-table
|
||||
A fast, lightweight, opinionated table and datagrid built on React
|
||||
|
||||
[](https://react-table-slack.herokuapp.com/)
|
||||
[](https://travis-ci.org/tannerlinsley/react-table)
|
||||
[](https://react-table-slack.herokuapp.com/)
|
||||
|
||||
## Features
|
||||
|
||||
- Lightweight at 3kb (and just 2kb more for styles)
|
||||
- No composition needed
|
||||
- Uses customizable JSX and callbacks for everything
|
||||
- Client-side pagination and sorting
|
||||
- Server-side support
|
||||
- Fully customizable JSX and callbacks for everything
|
||||
- Supports both Client-side & Server-side pagination and sorting
|
||||
- Minimal design & easily themeable
|
||||
|
||||
Why you may **not** want to use this component:
|
||||
- No support for infinite scrolling. We chose to avoid the complex problems that come with it, and instead provide reliable and predictable pagination.
|
||||
- You need tables built in something other than traditional HTML table elements.
|
||||
|
||||
## [Demo](http://react-table.zabapps.com)
|
||||
|
||||
## Table of Contents
|
||||
- [Installation](#nstallation)
|
||||
- [Example](#example)
|
||||
- [Data](#data)
|
||||
- [Default Props](#default-props)
|
||||
- [Columns](#columns)
|
||||
- [Styles](#styles)
|
||||
- [Header Groups](#header-groups)
|
||||
- [Server-side Data](#server-side-data)
|
||||
- [Multi-sort](#multi-sort)
|
||||
- [Component Overrides](#component-overrides)
|
||||
|
||||
<a name="installation"></a>
|
||||
## Installation
|
||||
```bash
|
||||
$ npm install react-table
|
||||
```
|
||||
|
||||
#### Styles
|
||||
React-table is built to be dropped into existing applications or styled from the ground up, but if you'd like a decent starting point, you can optionally include our default theme `react-table.css`. We think it looks great, honestly :)
|
||||
|
||||
## Quick Usage
|
||||
<a name="example"></a>
|
||||
## Example
|
||||
```javascript
|
||||
import ReactTable from 'react-table'
|
||||
|
||||
@@ -61,70 +69,42 @@ const columns = [{
|
||||
/>
|
||||
```
|
||||
|
||||
## Client-side Data
|
||||
To use client-side data, simply pass the `data` prop an array. Client-side filtering and pagination is built in, and your table will update gracefully if you change any props.
|
||||
<a name="data"></a>
|
||||
## Data
|
||||
Every React-Table instance requires you to set the `data` prop. To use client-side data, simply pass the `data` prop anything that resembles an array or object. Client-side filtering and pagination is built in, and your table will update gracefully if you change any props. [Server-side data](#server-side-data) is also supported.
|
||||
|
||||
## Server-side Data
|
||||
If you want to handle pagination, and sorting on the server, `react-table` makes it easy on you. Instead of passing the `data` prop an array, you provide a function instead.
|
||||
|
||||
This function will be called on mount, pagination events, and sorting events. It also provides you all of the parameters to help you query and format your data.
|
||||
|
||||
```javascript
|
||||
<ReactTable
|
||||
data={(params, callback) => {
|
||||
|
||||
// params will give you all the info you need to query and sort your data
|
||||
params == {
|
||||
page: 0, // The page index the user is requesting
|
||||
pageSize: 20, // The current pageSize
|
||||
pages: -1, // The amount of existing pages (-1 means there is no page data yet)
|
||||
sorting: [ // An array of column sort models (yes, you can multi-sort!)
|
||||
{
|
||||
id: 'columnID', // The columnID (usually the accessor string, but can be overridden for server-side or required if the column accessor is a function)
|
||||
ascending: true or false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Query your data however you'd like, then structure your response like so:
|
||||
const result = {
|
||||
rows: [...], // Your data for the current page/sorting model
|
||||
pages: 10 // optionally provide how many pages exist (this is only needed if you choose to display page numbers, and only the first time you make the call or if the page count changes)
|
||||
}
|
||||
|
||||
// You can return a promise that resolve the result
|
||||
return Axios.post('/myDataEnpoint', params) // resolves to `result`
|
||||
|
||||
// or use the manual callback whenever you please
|
||||
setTimeout(() => {
|
||||
callback(result)
|
||||
}, 5000)
|
||||
|
||||
// That's it!
|
||||
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## Multi-Sort
|
||||
When clicking on a column header, hold shift to multi-sort! You can toggle `aascending` `descending` and `none` for multi-sort columns. Clicking on a header without holding shift will clear the multi-sort and replace it with the single sort of that column. It's quite handy!
|
||||
|
||||
<a name="default-props"></a>
|
||||
## Default Props
|
||||
These are the default props for the main react component `<ReactTable />`
|
||||
```javascript
|
||||
{
|
||||
className: '-striped -highlight',
|
||||
// General
|
||||
pageSize: 20,
|
||||
minRows: 0,
|
||||
data: [],
|
||||
previousComponent: <button {...props} className='-btn'>{props.children}</button>,
|
||||
nextComponent: <button {...props} className='-btn'>{props.children}</button>,
|
||||
minRows: 0, // Ensure this many rows are always rendered, regardless of rows on page
|
||||
|
||||
// Text
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next',
|
||||
loadingComponent: <span>Loading...</span>,
|
||||
column: { // default properties for every column's model
|
||||
sortable: true,
|
||||
show: true
|
||||
}
|
||||
nextText: 'Next'
|
||||
|
||||
// Classes
|
||||
className: '-striped -highlight', // The most top level className for the component
|
||||
tableClassName: '', // ClassName for the `table` element
|
||||
theadClassName: '', // ClassName for the `thead` element
|
||||
tbodyClassName: '', // ClassName for the `tbody` element
|
||||
trClassName: '', // ClassName for all `tr` elements
|
||||
trClassCallback: row => null, // A call back to dynamically add classes (via the classnames module) to a row element
|
||||
paginationClassName: '' // ClassName for `pagination` element
|
||||
// Styles
|
||||
style: {}, // Main style object for the component
|
||||
tableStyle: {}, // style object for the `table` component
|
||||
theadStyle: {}, // style object for the `thead` component
|
||||
tbodyStyle: {}, // style object for the `tbody` component
|
||||
trStyle: {}, // style object for the `tr` component
|
||||
trStyleCallback: row => {}, // A call back to dynamically add styles to a row element
|
||||
thStyle: {}, // style object for the `th` component
|
||||
tdStyle: {}, // style object for the `td` component
|
||||
paginationStyle: {}, // style object for the `paginination` component
|
||||
}
|
||||
```
|
||||
|
||||
@@ -140,38 +120,60 @@ Object.assign(ReactTableDefaults, {
|
||||
})
|
||||
```
|
||||
|
||||
Or just define them on the component
|
||||
Or just define them on the component per-instance
|
||||
|
||||
```javascript
|
||||
<ReactTable
|
||||
pageSize={10}
|
||||
minRows={3}
|
||||
// etc...
|
||||
})
|
||||
/>
|
||||
```
|
||||
|
||||
## Column Props
|
||||
<a name="columns"></a>
|
||||
## Columns
|
||||
Every React-Table instance requires a `columns` prop, which is an array of objects containing the following properties
|
||||
|
||||
```javascript
|
||||
[{
|
||||
// Required
|
||||
header: 'Header Name' or JSX eg. ({data, column}) => <div>Header Name</div>,
|
||||
// General
|
||||
accessor: 'propertyName' or Accessor eg. (row) => row.propertyName,
|
||||
|
||||
// A unique ID is required if the accessor is not a string or if you would like to override the column name used in server-side calls
|
||||
id: 'myProperty',
|
||||
|
||||
// Optional
|
||||
columns: [...] // See Header Groups section below
|
||||
render: JSX eg. ({row, value, index}) => <span>{value}</span>, // Provide a JSX element or stateless function to render whatever you want as the column's cell with access to the entire row
|
||||
id: 'myProperty', // Conditional - A unique ID is required if the accessor is not a string or if you would like to override the column name used in server-side calls
|
||||
sortable: true,
|
||||
sort: 'asc' or 'desc',
|
||||
show: true,
|
||||
width: Number, // Locks the column width to this amount
|
||||
minWidth: Number // Allows the column to flex above this minimum amount
|
||||
|
||||
// Cell Options
|
||||
className: '', // Set the classname of the `td` element of the column
|
||||
style: {}, // Set the style of the `td` element of the column
|
||||
innerClassName: '', // Set the classname of the `.-td-inner` element of the column
|
||||
innerStyle: {}, // Set the style of the `.-td-inner` element of the column
|
||||
render: JSX eg. ({value, rowValues, row, index, viewIndex}) => <span>{value}</span>, // Provide a JSX element or stateless function to render whatever you want as the column's cell with access to the entire row
|
||||
// value == the accessed value of the column
|
||||
// rowValues == an object of all of the accessed values for the row
|
||||
// row == the original row of data supplied to the table
|
||||
// index == the original index of the data supplied to the table
|
||||
// viewIndex == the index of the row in the current page
|
||||
|
||||
// Header & HeaderGroup Options
|
||||
header: 'Header Name' or JSX eg. ({data, column}) => <div>Header Name</div>,
|
||||
headerClassName: '', // Set the classname of the `th` element of the column
|
||||
headerStyle: {}, // Set the style of the `th` element of the column
|
||||
headerInnerClassName: '', // Set the classname of the `.-th-inner` element of the column
|
||||
headerInnerStyle: {}, // Set the style of the `.th-inner` element of the column
|
||||
|
||||
// Header Groups only
|
||||
columns: [...] // See Header Groups section below
|
||||
|
||||
}]
|
||||
```
|
||||
|
||||
<a name="styles"></a>
|
||||
## Styles
|
||||
React-table is built to be dropped into existing applications or styled from the ground up, but if you'd like a decent starting point, you can optionally include our default theme `react-table.css`. We think it looks great, honestly :)
|
||||
|
||||
<a name="header-groups"></a>
|
||||
## Header Groups
|
||||
To group columns with another header column, just nest your columns in a header column like so:
|
||||
```javascript
|
||||
@@ -190,20 +192,91 @@ const columns = [{
|
||||
}]
|
||||
```
|
||||
|
||||
<a name="server-side-data"></a>
|
||||
## Server-side Data
|
||||
If you want to handle pagination, and sorting on the server, `react-table` makes it easy on you.
|
||||
|
||||
1. Feed React Table `data` from somewhere dynamic. eg. `state`, a redux store, etc...
|
||||
1. Add `manual` as a prop. This informs React Table that you'll be handling sorting and pagination server-side
|
||||
1. Subscribe to the `onChange` prop. This function is called any time sorting or pagination is changed by the user
|
||||
1. In the `onChange` callback, request your data using the provided information in the params of the function (state and instance)
|
||||
1. Update your data with the rows to be displayed
|
||||
1. Optionally set how many pages there are total
|
||||
|
||||
```javascript
|
||||
<ReactTable
|
||||
...
|
||||
data={this.state.data} // should default to []
|
||||
pages={this.state.pages} // should default to -1 (which means we don't know how many pages we have)
|
||||
manual // informs React Table that you'll be handling sorting and pagination server-side
|
||||
onChange={(state, instance) => {
|
||||
Axios.post('mysite.com/data', {
|
||||
page: state.page,
|
||||
pageSize: state.pageSize,
|
||||
sorting: state.sorting
|
||||
})
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
data: res.data.rows,
|
||||
pages: res.data.pages
|
||||
})
|
||||
})
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
For a detailed example, take a peek at our [async table mockup](https://github.com/tannerlinsley/react-table/blob/master/example/src/screens/async.js)
|
||||
|
||||
<a name="multi-sort"></a>
|
||||
## Multi-Sort
|
||||
When clicking on a column header, hold shift to multi-sort! You can toggle `ascending` `descending` and `none` for multi-sort columns. Clicking on a header without holding shift will clear the multi-sort and replace it with the single sort of that column. It's quite handy!
|
||||
|
||||
<a name="component-overrides"></a>
|
||||
## Component Overrides
|
||||
Though we wouldn't suggest it, `react-table` has the ability to change the core componentry it used to render it's table. You can do so by assigning a react component to it's corresponding global prop, or on a one-off basis like so:
|
||||
```javascript
|
||||
// Change the global default
|
||||
import { ReactTableDefaults } from 'react-table'
|
||||
Object.assign(ReactTableDefaults, {
|
||||
tableComponent: Component,
|
||||
theadComponent: Component,
|
||||
tbodyComponent: Component,
|
||||
trComponent: Component,
|
||||
thComponent: Component,
|
||||
tdComponent: Component,
|
||||
paginationComponent: Component,
|
||||
previousComponent: Component,
|
||||
nextComponent: Component,
|
||||
loadingComponent: Component
|
||||
})
|
||||
|
||||
// Or change per instance
|
||||
<ReactTable
|
||||
tableComponent={Component},
|
||||
theadComponent={Component},
|
||||
// etc...
|
||||
/>
|
||||
```
|
||||
|
||||
If you choose to change the core components React-Table uses to render, you must make sure your components support and utilized all of the neeeded features for that component to work properly. For a broad reference on how to do this, investigate [the source](https://github.com/tannerlinsley/react-table/blob/master/src/index.js) for the component you wish to replace.
|
||||
|
||||
|
||||
## Contributing
|
||||
To suggest a feature, create an issue if it does not already exist.
|
||||
If you would like to help develop a suggested feature follow these steps:
|
||||
|
||||
- Fork this repo
|
||||
- Run `npm install`
|
||||
- Run `npm watch`
|
||||
- `npm install`
|
||||
- `npm watch`
|
||||
- Implement your changes to files in the `src/` directory
|
||||
- Submit PR for review
|
||||
|
||||
If you would like to preview your changes, you can utilize the example like so:
|
||||
If you would like to preview your changes, you can link and utilize the example like so:
|
||||
|
||||
- `npm link`
|
||||
- `cd example`
|
||||
- Run `npm install`
|
||||
- Run `npm watch`
|
||||
- `npm install`
|
||||
- `npm link react-table`
|
||||
- `npm watch`
|
||||
- Make changes to the example in `src/screens/index.js` if needed
|
||||
- View changes at `localhost:8000`
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Vendored
+18
-9
@@ -100,12 +100,7 @@ html {
|
||||
min-height: 100vh;
|
||||
}
|
||||
body {
|
||||
background: #003952;
|
||||
background: -webkit-radial-gradient(ellipse at center, #005275 0%, #003952 100%);
|
||||
background: -moz-radial-gradient(ellipse at center, #005275 0%, #003952 100%);
|
||||
background: -o-radial-gradient(ellipse at center, #005275 0%, #003952 100%);
|
||||
background: -ms-radial-gradient(ellipse at center, #005275 0%, #003952 100%);
|
||||
background: radial-gradient(ellipse at center, #005275 0%, #003952 100%);
|
||||
background: #fff;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 300;
|
||||
padding-bottom: 50px;
|
||||
@@ -115,12 +110,16 @@ h1 {
|
||||
color: #fff;
|
||||
}
|
||||
a {
|
||||
color: #fff;
|
||||
color: #02bfe6;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
.logo {
|
||||
width: 400px;
|
||||
}
|
||||
.container {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
@@ -151,12 +150,22 @@ strong {
|
||||
}
|
||||
.container .github-addon {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
}
|
||||
.container .table-wrap {
|
||||
background: #fff;
|
||||
width: 700px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: 0 0 20px 0 rgba(0,0,0,0.2);
|
||||
box-shadow: 0 0 20px 0 rgba(0,0,0,0.2);
|
||||
}
|
||||
.ReactTable thead th.-sort-asc,
|
||||
.ReactTable thead td.-sort-asc {
|
||||
-webkit-box-shadow: inset 0 3px 0 0 rgba(0,0,0,0.6);
|
||||
box-shadow: inset 0 3px 0 0 rgba(0,0,0,0.6);
|
||||
}
|
||||
.ReactTable thead th.-sort-desc,
|
||||
.ReactTable thead td.-sort-desc {
|
||||
-webkit-box-shadow: inset 0 -3px 0 0 rgba(0,0,0,0.6);
|
||||
box-shadow: inset 0 -3px 0 0 rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
Vendored
+730
-24
File diff suppressed because one or more lines are too long
@@ -1,4 +1,5 @@
|
||||
import { Render } from 'jumpsuit'
|
||||
// import App from 'screens/async'
|
||||
import App from 'screens/index'
|
||||
|
||||
Render(null, <App />)
|
||||
|
||||
+17
-8
@@ -18,9 +18,7 @@ global-reset()
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
$fnt-open-sans = 'Open Sans', sans-serif
|
||||
$clr-jumpsuit1 = #003952
|
||||
$clr-jumpsuit2 = #005275
|
||||
$clr-jumpsuit3 = #0082bb
|
||||
$react = #25d8fd
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// styles
|
||||
@@ -33,8 +31,7 @@ html
|
||||
min-height 100vh
|
||||
|
||||
body
|
||||
background: $clr-jumpsuit1
|
||||
background: radial-gradient(ellipse at center, $clr-jumpsuit2 0%, $clr-jumpsuit1 100%)
|
||||
background: white
|
||||
font-family: $fnt-open-sans
|
||||
font-weight: 300
|
||||
padding-bottom: 50px
|
||||
@@ -44,12 +41,16 @@ h1
|
||||
color: white
|
||||
|
||||
a
|
||||
color: white
|
||||
color: darken($react, 20%)
|
||||
font-weight: bold
|
||||
text-decoration: none
|
||||
|
||||
strong
|
||||
font-weight: bold
|
||||
|
||||
.logo
|
||||
width: 400px
|
||||
|
||||
.container
|
||||
display: flex
|
||||
flex-direction: column
|
||||
@@ -60,11 +61,19 @@ strong
|
||||
|
||||
.github-addon
|
||||
font-size: 20px
|
||||
color:white
|
||||
padding: 10px
|
||||
|
||||
.table-wrap
|
||||
background: white
|
||||
width: 700px
|
||||
padding: 10px
|
||||
border-radius: 5px
|
||||
box-shadow: 0 0 20px 0 alpha(black, .2)
|
||||
|
||||
.ReactTable
|
||||
thead
|
||||
th
|
||||
td
|
||||
&.-sort-asc
|
||||
box-shadow:inset 0 3px 0 0 alpha(black, .6)
|
||||
&.-sort-desc
|
||||
box-shadow:inset 0 -3px 0 0 alpha(black, .6)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,134 @@
|
||||
import { Component } from 'jumpsuit'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from 'react-table'
|
||||
|
||||
// Let's mock some data to play around with
|
||||
const rawData = _.map(_.range(1000), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
|
||||
// Now let's mock the server. It's job is simple: use the table model to sort and return the page data
|
||||
const requestData = (pageSize, page, sorting) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// On the server, you'll likely use SQL or noSQL or some other query language to do this.
|
||||
// For this mock, we'll just use lodash
|
||||
const sortedData = _.orderBy(rawData, sorting.map(sort => {
|
||||
return row => {
|
||||
if (row[sort.id] === null || row[sort.id] === undefined) {
|
||||
return -Infinity
|
||||
}
|
||||
return typeof row[sort.id] === 'string' ? row[sort.id].toLowerCase() : row[sort.id]
|
||||
}
|
||||
}), sorting.map(d => d.asc ? 'asc' : 'desc'))
|
||||
|
||||
// Be sure to send back the rows to be displayed and any other pertinent information, like how many pages there are total.
|
||||
const res = {
|
||||
rows: sortedData.slice(pageSize * page, (pageSize * page) + pageSize),
|
||||
pages: Math.ceil(rawData.length / pageSize)
|
||||
}
|
||||
|
||||
// Here we'll simulate a server response with 500ms of delay.
|
||||
setTimeout(() => resolve(res), 500)
|
||||
})
|
||||
}
|
||||
|
||||
export default Component({
|
||||
getInitialState () {
|
||||
// To handle our data server-side, we need a few things in the state to help us out:
|
||||
return {
|
||||
data: [],
|
||||
pages: null,
|
||||
loading: true
|
||||
}
|
||||
},
|
||||
fetchData (state, instance) {
|
||||
// Whenever the table model changes, or the user sorts or changes pages, this method gets called and passed the current table model.
|
||||
// You can set the `loading` prop of the table to true to use the built-in one or show you're own loading bar if you want.
|
||||
this.setState({loading: true})
|
||||
// Request the data however you want. Here, we'll use our mocked service we created earlier
|
||||
requestData(state.pageSize, state.page, state.sorting)
|
||||
.then((res) => {
|
||||
// Now just get the rows of data to your React Table (and update anything else like total pages or loading)
|
||||
this.setState({
|
||||
data: res.rows,
|
||||
pages: res.pages,
|
||||
loading: false
|
||||
})
|
||||
})
|
||||
},
|
||||
render () {
|
||||
return (
|
||||
<div className='container'>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<h1>
|
||||
<span style={{position: 'absolute', textIndent: '-9999em'}}>
|
||||
react-table <strong>server-side demo</strong>
|
||||
</span>
|
||||
<img src='/Banner.png' className='logo' />
|
||||
</h1>
|
||||
<br />
|
||||
<div>
|
||||
<a
|
||||
className='github-button'
|
||||
href='https://github.com/tannerlinsley/react-table'
|
||||
target='_blank'
|
||||
data-style='mega'
|
||||
data-count-href='/tannerlinsley/react-table/stargazers'
|
||||
data-count-api='/repos/tannerlinsley/react-table#stargazers_count'
|
||||
data-count-aria-label='# stargazers on GitHub'
|
||||
aria-label='Star tannerlinsley/react-table on GitHub'>
|
||||
Star
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<div className='github-addon'>
|
||||
<a
|
||||
target='_blank'
|
||||
href='https://github.com/tannerlinsley/react-table'>
|
||||
View on Github
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
columns={[{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]}
|
||||
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
|
||||
pageSize={5}
|
||||
data={this.state.data} // Set the rows to be displayed
|
||||
pages={this.state.pages} // Display the total number of pages
|
||||
loading={this.state.loading} // Display the loading overlay when we need it
|
||||
onChange={this.fetchData} // Request new data when things change
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -21,7 +21,8 @@ export default Component({
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
accessor: 'lastName'
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
@@ -34,14 +35,18 @@ export default Component({
|
||||
return (
|
||||
<div className='container'>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<h1>react-table <strong>demo</strong></h1>
|
||||
<h1>
|
||||
<span style={{position: 'absolute', textIndent: '-9999em'}}>
|
||||
react-table <strong>demo</strong>
|
||||
</span>
|
||||
<img src='/Banner.png' className='logo' />
|
||||
</h1>
|
||||
<br />
|
||||
<div>
|
||||
<a
|
||||
className='github-button'
|
||||
href='https://github.com/tannerlinsley/react-table'
|
||||
target='_blank'
|
||||
data-icon='octicon-star'
|
||||
data-style='mega'
|
||||
data-count-href='/tannerlinsley/react-table/stargazers'
|
||||
data-count-api='/repos/tannerlinsley/react-table#stargazers_count'
|
||||
@@ -50,6 +55,7 @@ export default Component({
|
||||
Star
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<div className='github-addon'>
|
||||
<a
|
||||
target='_blank'
|
||||
@@ -66,9 +72,9 @@ export default Component({
|
||||
columns={columns}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center', color: 'white'}}>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting for multi-sort!</em>
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
+25
-8
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-table",
|
||||
"version": "2.0.1",
|
||||
"version": "3.0.0",
|
||||
"description": "A fast, lightweight, opinionated table and datagrid built on React",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/tannerlinsley/react-table#readme",
|
||||
@@ -14,16 +14,26 @@
|
||||
"react-table",
|
||||
"datagrid"
|
||||
],
|
||||
"main": "react-table.js",
|
||||
"main": "lib/index.js",
|
||||
"files": [
|
||||
"lib/",
|
||||
"react-table.js",
|
||||
"react-table.css",
|
||||
"media/*.png"
|
||||
],
|
||||
"scripts": {
|
||||
"build:js": "rm -rf react-table.js && browserify src/index.js -s reactTable -u react -t babelify -g uglifyify -o react-table.js",
|
||||
"build:node": "babel src --out-dir lib --source-maps inline",
|
||||
"build:css": "rm -rf react-table.css && stylus src/index.styl --use ./node_modules/nib/lib/nib.js --compress -o react-table.css",
|
||||
"watch": "onchange 'src/**' -i -- npm-run-all build:*",
|
||||
"prepublish": "npm-run-all build:*"
|
||||
"test": "standard",
|
||||
"umd": "rm -rf react-table.js && browserify lib/index.js -s reactTable -x react -t babelify -g uglifyify -o react-table.js",
|
||||
"prepublish": "npm-run-all build:* && npm run umd",
|
||||
"postpublish": "git push --tags"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5",
|
||||
"lodash": "^4.16.4",
|
||||
"classnames": "^2.2.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -34,16 +44,23 @@
|
||||
"babel-preset-stage-2": "6.13.0",
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "13.1.0",
|
||||
"nib": "^1.1.2",
|
||||
"npm-run-all": "^3.1.1",
|
||||
"onchange": "^3.0.2",
|
||||
"react": "15.3.1",
|
||||
"standard": "8.0.0",
|
||||
"standard": "^8.0.0",
|
||||
"stylus": "^0.54.5",
|
||||
"uglifyify": "3.0.3"
|
||||
},
|
||||
"browserify-shim": {
|
||||
"react": "global:React"
|
||||
},
|
||||
"standard": {
|
||||
"parser": "babel-eslint",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"lib",
|
||||
"example",
|
||||
"react-table.js"
|
||||
]
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
Vendored
-7
File diff suppressed because one or more lines are too long
+297
-320
@@ -1,34 +1,69 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
const _ = {
|
||||
get,
|
||||
takeRight,
|
||||
last,
|
||||
orderBy,
|
||||
range,
|
||||
clone,
|
||||
remove
|
||||
}
|
||||
import _ from './utils'
|
||||
|
||||
const defaultButton = (props) => (
|
||||
<button {...props} className='-btn'>{props.children}</button>
|
||||
)
|
||||
|
||||
export const ReactTableDefaults = {
|
||||
className: '-striped -highlight',
|
||||
// State
|
||||
data: [],
|
||||
loading: false,
|
||||
pageSize: 20,
|
||||
minRows: 0,
|
||||
data: [],
|
||||
previousComponent: defaultButton,
|
||||
nextComponent: defaultButton,
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next',
|
||||
loadingComponent: <span>Loading...</span>,
|
||||
// Callbacks
|
||||
onChange: () => null,
|
||||
onPageChange: () => null,
|
||||
onSort: () => null,
|
||||
// Classes
|
||||
className: '-striped -highlight',
|
||||
tableClassName: '',
|
||||
theadClassName: '',
|
||||
tbodyClassName: '',
|
||||
trClassName: '',
|
||||
trClassCallback: d => null,
|
||||
thClassName: '',
|
||||
thGroupClassName: '',
|
||||
tdClassName: '',
|
||||
paginationClassName: '',
|
||||
// Styles
|
||||
style: {},
|
||||
tableStyle: {},
|
||||
theadStyle: {},
|
||||
tbodyStyle: {},
|
||||
trStyle: {},
|
||||
trStyleCallback: d => {},
|
||||
thStyle: {},
|
||||
tdStyle: {},
|
||||
paginationStyle: {},
|
||||
// Global Column Defaults
|
||||
column: {
|
||||
sortable: true,
|
||||
show: true
|
||||
}
|
||||
show: true,
|
||||
className: '',
|
||||
style: {},
|
||||
innerClassName: '',
|
||||
innerStyle: {},
|
||||
headerClassName: '',
|
||||
headerStyle: {},
|
||||
headerInnerClassName: '',
|
||||
headerInnerStyle: {}
|
||||
},
|
||||
// Text
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next',
|
||||
loadingText: 'Loading...',
|
||||
// Components
|
||||
tableComponent: (props) => <table {...props}>{props.children}</table>,
|
||||
theadComponent: (props) => <thead {...props}>{props.children}</thead>,
|
||||
tbodyComponent: (props) => <tbody {...props}>{props.children}</tbody>,
|
||||
trComponent: (props) => <tr {...props}>{props.children}</tr>,
|
||||
thComponent: (props) => <th {...props}>{props.children}</th>,
|
||||
tdComponent: (props) => <td {...props}>{props.children}</td>,
|
||||
previousComponent: null,
|
||||
nextComponent: null
|
||||
}
|
||||
|
||||
export default React.createClass({
|
||||
@@ -37,85 +72,27 @@ export default React.createClass({
|
||||
},
|
||||
getInitialState () {
|
||||
return {
|
||||
page: 0,
|
||||
pages: -1,
|
||||
sorting: false
|
||||
}
|
||||
},
|
||||
componentWillMount () {
|
||||
this.update(this.props)
|
||||
componentDidMount () {
|
||||
this.fireOnChange()
|
||||
},
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.update(nextProps)
|
||||
fireOnChange () {
|
||||
this.props.onChange({
|
||||
page: _.getFirstDefined(this.props.page, this.state.page),
|
||||
pageSize: this.props.pageSize,
|
||||
pages: this.props.pages,
|
||||
sorting: this.getSorting()
|
||||
}, this)
|
||||
},
|
||||
update (props) {
|
||||
const resetState = {
|
||||
loading: false,
|
||||
page: 0,
|
||||
pages: -1
|
||||
// columns: {} for column hiding in the future
|
||||
getInitSorting (columns) {
|
||||
if (!columns) {
|
||||
return []
|
||||
}
|
||||
this.setState(resetState)
|
||||
const newState = Object.assign({}, this.state, resetState)
|
||||
this.isAsync = typeof props.data === 'function'
|
||||
this.buildColumns(props, newState)
|
||||
this.buildData(props, newState)
|
||||
},
|
||||
buildColumns (props) {
|
||||
this.hasHeaderGroups = false
|
||||
props.columns.forEach(column => {
|
||||
if (column.columns) {
|
||||
this.hasHeaderGroups = true
|
||||
}
|
||||
})
|
||||
|
||||
this.headerGroups = []
|
||||
this.decoratedColumns = []
|
||||
let currentSpan = []
|
||||
|
||||
const addHeader = (columns, column = {}) => {
|
||||
this.headerGroups.push(Object.assign({}, column, {
|
||||
columns: columns
|
||||
}))
|
||||
currentSpan = []
|
||||
}
|
||||
const makeDecoratedColumn = (column) => {
|
||||
const dcol = Object.assign({}, this.props.column, column)
|
||||
if (typeof dcol.accessor === 'string') {
|
||||
dcol.id = dcol.id || dcol.accessor
|
||||
const accessorString = dcol.accessor
|
||||
dcol.accessor = row => _.get(row, accessorString)
|
||||
}
|
||||
if (!dcol.id) {
|
||||
console.warn('No column ID found for column: ', dcol)
|
||||
}
|
||||
if (!dcol.accessor) {
|
||||
console.warn('No column accessor found for column: ', dcol)
|
||||
}
|
||||
return dcol
|
||||
}
|
||||
|
||||
props.columns.forEach((column, i) => {
|
||||
if (column.columns) {
|
||||
column.columns.forEach(nestedColumn => {
|
||||
this.decoratedColumns.push(makeDecoratedColumn(nestedColumn))
|
||||
})
|
||||
if (this.hasHeaderGroups) {
|
||||
if (currentSpan.length > 0) {
|
||||
addHeader(currentSpan)
|
||||
}
|
||||
addHeader(_.takeRight(this.decoratedColumns, column.columns.length), column)
|
||||
}
|
||||
} else {
|
||||
this.decoratedColumns.push(makeDecoratedColumn(column))
|
||||
currentSpan.push(_.last(this.decoratedColumns))
|
||||
}
|
||||
})
|
||||
|
||||
if (this.hasHeaderGroups && currentSpan.length > 0) {
|
||||
addHeader(currentSpan)
|
||||
}
|
||||
},
|
||||
getInitSorting () {
|
||||
const initSorting = this.decoratedColumns.filter(d => {
|
||||
const initSorting = columns.filter(d => {
|
||||
return typeof d.sort !== 'undefined'
|
||||
}).map(d => {
|
||||
return {
|
||||
@@ -125,97 +102,109 @@ export default React.createClass({
|
||||
})
|
||||
|
||||
return initSorting.length ? initSorting : [{
|
||||
id: this.decoratedColumns[0].id,
|
||||
id: columns[0].id,
|
||||
asc: true
|
||||
}]
|
||||
},
|
||||
buildData (props, state) {
|
||||
const sorting = state.sorting === false ? this.getInitSorting() : state.sorting
|
||||
|
||||
const setData = (data) => {
|
||||
this.setState({
|
||||
sorting,
|
||||
data,
|
||||
page: state.page,
|
||||
loading: false
|
||||
})
|
||||
}
|
||||
|
||||
if (this.isAsync) {
|
||||
this.setState({
|
||||
loading: true
|
||||
})
|
||||
|
||||
const cb = (res) => {
|
||||
if (!res) {
|
||||
return Promise.reject('Uh Oh! Nothing was returned in ReactTable\'s data callback!')
|
||||
}
|
||||
if (res.pages) {
|
||||
this.setState({
|
||||
pages: res.pages
|
||||
})
|
||||
}
|
||||
// Only access the data. Sorting is done server side.
|
||||
const accessedData = this.accessData(res.rows)
|
||||
setData(accessedData)
|
||||
}
|
||||
|
||||
// Fetch data with current state
|
||||
const dataRes = props.data({
|
||||
sorting,
|
||||
page: state.page || 0,
|
||||
pageSize: props.pageSize,
|
||||
pages: state.pages
|
||||
}, cb)
|
||||
|
||||
if (dataRes && dataRes.then) {
|
||||
dataRes.then(cb)
|
||||
}
|
||||
} else {
|
||||
// Return locally accessed, sorted data
|
||||
const accessedData = this.accessData(props.data)
|
||||
const sortedData = this.sortData(accessedData, sorting)
|
||||
setData(sortedData)
|
||||
}
|
||||
},
|
||||
accessData (data) {
|
||||
return data.map((d) => {
|
||||
const row = {
|
||||
__original: d
|
||||
}
|
||||
this.decoratedColumns.forEach(column => {
|
||||
row[column.id] = column.accessor(d)
|
||||
})
|
||||
return row
|
||||
})
|
||||
},
|
||||
sortData (data, sorting) {
|
||||
const resolvedSorting = sorting.length ? sorting : this.getInitSorting()
|
||||
return _.orderBy(data, resolvedSorting.map(sort => {
|
||||
return _.orderBy(data, sorting.map(sort => {
|
||||
return row => {
|
||||
if (row[sort.id] === null || row[sort.id] === undefined) {
|
||||
return -Infinity
|
||||
}
|
||||
return typeof row[sort.id] === 'string' ? row[sort.id].toLowerCase() : row[sort.id]
|
||||
}
|
||||
}), resolvedSorting.map(d => d.asc ? 'asc' : 'desc'))
|
||||
}), sorting.map(d => d.asc ? 'asc' : 'desc'))
|
||||
},
|
||||
setPage (page) {
|
||||
if (this.isAsync) {
|
||||
return this.buildData(this.props, Object.assign({}, this.state, {page}))
|
||||
makeDecoratedColumn (column) {
|
||||
const dcol = Object.assign({}, this.props.column, column)
|
||||
|
||||
if (typeof dcol.accessor === 'string') {
|
||||
dcol.id = dcol.id || dcol.accessor
|
||||
const accessorString = dcol.accessor
|
||||
dcol.accessor = row => _.get(row, accessorString)
|
||||
return dcol
|
||||
}
|
||||
this.setState({
|
||||
page
|
||||
})
|
||||
|
||||
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.')
|
||||
}
|
||||
|
||||
if (!dcol.accessor) {
|
||||
dcol.accessor = d => undefined
|
||||
}
|
||||
|
||||
return dcol
|
||||
},
|
||||
getSorting (columns) {
|
||||
return this.props.sorting || (this.state.sorting && this.state.sorting.length ? this.state.sorting : this.getInitSorting(columns))
|
||||
},
|
||||
|
||||
render () {
|
||||
const data = this.state.data ? this.state.data : []
|
||||
// Build Columns
|
||||
const decoratedColumns = []
|
||||
const headerGroups = []
|
||||
let currentSpan = []
|
||||
|
||||
const pagesLength = this.isAsync ? this.state.pages : Math.ceil(data.length / this.props.pageSize)
|
||||
// Determine Header Groups
|
||||
let hasHeaderGroups = false
|
||||
this.props.columns
|
||||
.forEach(column => {
|
||||
if (column.columns) {
|
||||
hasHeaderGroups = true
|
||||
}
|
||||
})
|
||||
|
||||
// A convenience function to add a header and reset the currentSpan
|
||||
const addHeader = (columns, column = {}) => {
|
||||
headerGroups.push(Object.assign({}, column, {
|
||||
columns: columns
|
||||
}))
|
||||
currentSpan = []
|
||||
}
|
||||
|
||||
// Build the columns and headers
|
||||
const visibleColumns = this.props.columns.filter(d => _.getFirstDefined(d.show, true))
|
||||
visibleColumns.forEach((column, i) => {
|
||||
if (column.columns) {
|
||||
const nestedColumns = column.columns.filter(d => _.getFirstDefined(d.show, true))
|
||||
nestedColumns.forEach(nestedColumn => {
|
||||
decoratedColumns.push(this.makeDecoratedColumn(nestedColumn))
|
||||
})
|
||||
if (hasHeaderGroups) {
|
||||
if (currentSpan.length > 0) {
|
||||
addHeader(currentSpan)
|
||||
}
|
||||
addHeader(_.takeRight(decoratedColumns, nestedColumns.length), column)
|
||||
}
|
||||
} else {
|
||||
decoratedColumns.push(this.makeDecoratedColumn(column))
|
||||
currentSpan.push(_.last(decoratedColumns))
|
||||
}
|
||||
})
|
||||
|
||||
if (hasHeaderGroups && currentSpan.length > 0) {
|
||||
addHeader(currentSpan)
|
||||
}
|
||||
|
||||
const sorting = this.getSorting(decoratedColumns)
|
||||
const accessedData = this.props.data.map((d, i) => {
|
||||
const row = {
|
||||
__original: d,
|
||||
__index: i
|
||||
}
|
||||
decoratedColumns.forEach(column => {
|
||||
row[column.id] = column.accessor(d)
|
||||
})
|
||||
return row
|
||||
})
|
||||
const data = this.props.manual ? accessedData : this.sortData(accessedData, sorting)
|
||||
|
||||
// Pagination
|
||||
const pagesLength = this.props.manual ? this.props.pages : Math.ceil(data.length / this.props.pageSize)
|
||||
const startRow = this.props.pageSize * this.state.page
|
||||
const endRow = startRow + this.props.pageSize
|
||||
const pageRows = this.isAsync ? data.slice(0, this.props.pageSize) : data.slice(startRow, endRow)
|
||||
const pageRows = this.props.manual ? data : data.slice(startRow, endRow)
|
||||
const padRows = pagesLength > 1 ? _.range(this.props.pageSize - pageRows.length)
|
||||
: this.props.minRows ? _.range(Math.max(this.props.minRows - pageRows.length, 0))
|
||||
: []
|
||||
@@ -223,22 +212,46 @@ export default React.createClass({
|
||||
const canPrevious = this.state.page > 0
|
||||
const canNext = this.state.page + 1 < pagesLength
|
||||
|
||||
const PreviousComponent = this.props.previousComponent
|
||||
const NextComponent = this.props.previousComponent
|
||||
const TableComponent = this.props.tableComponent
|
||||
const TheadComponent = this.props.theadComponent
|
||||
const TbodyComponent = this.props.tbodyComponent
|
||||
const TrComponent = this.props.trComponent
|
||||
const ThComponent = this.props.thComponent
|
||||
const TdComponent = this.props.tdComponent
|
||||
|
||||
const PreviousComponent = this.props.previousComponent || defaultButton
|
||||
const NextComponent = this.props.nextComponent || defaultButton
|
||||
|
||||
return (
|
||||
<div className={classnames(this.props.className, 'ReactTable')}>
|
||||
<table>
|
||||
{this.hasHeaderGroups && (
|
||||
<thead className='-headerGroups'>
|
||||
<tr>
|
||||
{this.headerGroups.map((column, i) => {
|
||||
<div
|
||||
className={classnames(this.props.className, 'ReactTable')}
|
||||
style={this.props.style}
|
||||
>
|
||||
<TableComponent
|
||||
className={classnames(this.props.tableClassName)}
|
||||
style={this.props.tableStyle}
|
||||
>
|
||||
{hasHeaderGroups && (
|
||||
<TheadComponent
|
||||
className={classnames(this.props.theadGroupClassName, '-headerGroups')}
|
||||
style={this.props.theadStyle}
|
||||
>
|
||||
<TrComponent
|
||||
className={this.props.trClassName}
|
||||
style={this.props.trStyle}
|
||||
>
|
||||
{headerGroups.map((column, i) => {
|
||||
return (
|
||||
<th
|
||||
<ThComponent
|
||||
key={i}
|
||||
colSpan={column.columns.length}>
|
||||
colSpan={column.columns.length}
|
||||
className={classnames(this.props.thClassname, column.headerClassName)}
|
||||
style={Object.assign({}, this.props.thStyle, column.headerStyle)}
|
||||
>
|
||||
<div
|
||||
className='-th-inner'>
|
||||
className={classnames(column.headerInnerClassName, '-th-inner')}
|
||||
style={Object.assign({}, this.props.thInnerStyle, column.headerInnerStyle)}
|
||||
>
|
||||
{typeof column.header === 'function' ? (
|
||||
<column.header
|
||||
data={this.props.data}
|
||||
@@ -246,36 +259,46 @@ export default React.createClass({
|
||||
/>
|
||||
) : column.header}
|
||||
</div>
|
||||
</th>
|
||||
</ThComponent>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
)}
|
||||
<thead>
|
||||
<tr>
|
||||
{this.decoratedColumns.map((column, i) => {
|
||||
const sort = this.state.sorting.find(d => d.id === column.id)
|
||||
<TheadComponent
|
||||
className={classnames(this.props.theadClassName)}
|
||||
style={this.props.theadStyle}
|
||||
>
|
||||
<TrComponent
|
||||
className={this.props.trClassName}
|
||||
style={this.props.trStyle}
|
||||
>
|
||||
{decoratedColumns.map((column, i) => {
|
||||
const sort = sorting.find(d => d.id === column.id)
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
return (
|
||||
<th
|
||||
<ThComponent
|
||||
key={i}
|
||||
className={classnames(
|
||||
this.props.thClassname,
|
||||
column.headerClassName,
|
||||
sort ? (sort.asc ? '-sort-asc' : '-sort-desc') : '',
|
||||
{
|
||||
'-cursor-pointer': column.sortable,
|
||||
'-hidden': !show
|
||||
}
|
||||
)}
|
||||
style={Object.assign({}, this.props.thStyle, column.headerStyle)}
|
||||
onClick={(e) => {
|
||||
column.sortable && this.sortColumn(column, e.shiftKey)
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='-th-inner'
|
||||
style={{
|
||||
width: column.width + 'px',
|
||||
className={classnames(column.headerInnerClassName, '-th-inner')}
|
||||
style={Object.assign({}, column.headerInnerStyle, {
|
||||
minWidth: column.minWidth + 'px'
|
||||
}}>
|
||||
})}
|
||||
>
|
||||
{typeof column.header === 'function' ? (
|
||||
<column.header
|
||||
data={this.props.data}
|
||||
@@ -283,72 +306,96 @@ export default React.createClass({
|
||||
/>
|
||||
) : column.header}
|
||||
</div>
|
||||
</th>
|
||||
</ThComponent>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
<TbodyComponent
|
||||
className={classnames(this.props.tbodyClassName)}
|
||||
style={this.props.tbodyStyle}
|
||||
>
|
||||
{pageRows.map((row, i) => {
|
||||
const rowInfo = {
|
||||
row: row.__original,
|
||||
rowValues: row,
|
||||
index: row.__index,
|
||||
viewIndex: i
|
||||
}
|
||||
return (
|
||||
<tr key={i}>
|
||||
{this.decoratedColumns.map((column, i2) => {
|
||||
<TrComponent
|
||||
key={i}
|
||||
className={classnames(this.props.trClassName, this.props.trClassCallback(rowInfo))}
|
||||
style={Object.assign({}, this.props.trStyle, this.props.trStyleCallback(rowInfo))}
|
||||
>
|
||||
{decoratedColumns.map((column, i2) => {
|
||||
const Cell = column.render
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
return (
|
||||
<td
|
||||
className={classnames({hidden: !show})}
|
||||
key={i2}>
|
||||
<TdComponent
|
||||
key={i2}
|
||||
className={classnames(column.className, {hidden: !show})}
|
||||
style={Object.assign({}, this.props.tdStyle, column.style)}
|
||||
>
|
||||
<div
|
||||
className='-td-inner'
|
||||
style={{
|
||||
width: column.width + 'px',
|
||||
className={classnames(column.innerClassName, '-td-inner')}
|
||||
style={Object.assign({}, column.innerStyle, {
|
||||
minWidth: column.minWidth + 'px'
|
||||
}}>
|
||||
})}
|
||||
>
|
||||
{typeof Cell === 'function' ? (
|
||||
<Cell
|
||||
value={row[column.id]}
|
||||
row={row.__original}
|
||||
index={i}
|
||||
{...rowInfo}
|
||||
value={rowInfo.rowValues[column.id]}
|
||||
/>
|
||||
) : typeof Cell !== 'undefined' ? Cell
|
||||
: row[column.id]}
|
||||
: rowInfo.rowValues[column.id]}
|
||||
</div>
|
||||
</td>
|
||||
</TdComponent>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
</TrComponent>
|
||||
)
|
||||
})}
|
||||
{padRows.map((row, i) => {
|
||||
return (
|
||||
<tr key={i}>
|
||||
{this.decoratedColumns.map((column, i2) => {
|
||||
<TrComponent
|
||||
key={i}
|
||||
className={classnames(this.props.trClassName, '-padRow')}
|
||||
style={this.props.trStyle}
|
||||
>
|
||||
{decoratedColumns.map((column, i2) => {
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
return (
|
||||
<td
|
||||
className={classnames({hidden: !show})}
|
||||
key={i2}>
|
||||
<TdComponent
|
||||
key={i2}
|
||||
className={classnames(column.className, {hidden: !show})}
|
||||
style={Object.assign({}, this.props.tdStyle, column.style)}
|
||||
>
|
||||
<div
|
||||
className='-td-inner'
|
||||
style={{
|
||||
width: column.width + 'px',
|
||||
className={classnames(column.innerClassName, '-td-inner')}
|
||||
style={Object.assign({}, column.innerStyle, {
|
||||
minWidth: column.minWidth + 'px'
|
||||
}}> </div>
|
||||
</td>
|
||||
})}
|
||||
> </div>
|
||||
</TdComponent>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
</TrComponent>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</TbodyComponent>
|
||||
</TableComponent>
|
||||
{pagesLength > 1 && (
|
||||
<div className='-pagination'>
|
||||
<div
|
||||
className={classnames(this.props.paginationClassName, '-pagination')}
|
||||
style={this.props.paginationStyle}
|
||||
>
|
||||
<div className='-left'>
|
||||
<PreviousComponent
|
||||
onClick={canPrevious && ((e) => this.previousPage(e))}
|
||||
disabled={!canPrevious}>
|
||||
disabled={!canPrevious}
|
||||
>
|
||||
{this.props.previousText}
|
||||
</PreviousComponent>
|
||||
</div>
|
||||
@@ -358,22 +405,39 @@ export default React.createClass({
|
||||
<div className='-right'>
|
||||
<NextComponent
|
||||
onClick={canNext && ((e) => this.nextPage(e))}
|
||||
disabled={!canNext}>
|
||||
disabled={!canNext}
|
||||
>
|
||||
{this.props.nextText}
|
||||
</NextComponent>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={classnames('-loading', {'-active': this.state.loading})}>
|
||||
<div className={classnames('-loading', {'-active': this.props.loading})}>
|
||||
<div className='-loading-inner'>
|
||||
{this.props.loadingComponent}
|
||||
{this.props.loadingText}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
// User actions
|
||||
setPage (page) {
|
||||
this.setState({
|
||||
page
|
||||
}, () => {
|
||||
this.fireOnChange()
|
||||
})
|
||||
},
|
||||
nextPage (e) {
|
||||
e.preventDefault()
|
||||
this.setPage(this.state.page + 1)
|
||||
},
|
||||
previousPage (e) {
|
||||
e.preventDefault()
|
||||
this.setPage(this.state.page - 1)
|
||||
},
|
||||
sortColumn (column, additive) {
|
||||
const existingSorting = this.state.sorting || []
|
||||
const existingSorting = this.getSorting()
|
||||
let sorting = _.clone(this.state.sorting || [])
|
||||
const existingIndex = sorting.findIndex(d => d.id === column.id)
|
||||
if (existingIndex > -1) {
|
||||
@@ -405,98 +469,11 @@ export default React.createClass({
|
||||
}
|
||||
}
|
||||
const page = (existingIndex === 0 || (!existingSorting.length && sorting.length) || !additive) ? 0 : this.state.page
|
||||
this.buildData(this.props, Object.assign({}, this.state, {page, sorting}))
|
||||
},
|
||||
nextPage (e) {
|
||||
e.preventDefault()
|
||||
this.setPage(this.state.page + 1)
|
||||
},
|
||||
previousPage (e) {
|
||||
e.preventDefault()
|
||||
this.setPage(this.state.page - 1)
|
||||
this.setState({
|
||||
page,
|
||||
sorting
|
||||
}, () => {
|
||||
this.fireOnChange()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
// ########################################################################
|
||||
// Utils
|
||||
// ########################################################################
|
||||
|
||||
|
||||
function remove (a, b) {
|
||||
return a.filter(function (o, i) {
|
||||
var r = b(o)
|
||||
if (r) {
|
||||
a.splice(i, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function get (a, b) {
|
||||
if (isArray(b)) {
|
||||
b = b.join('.')
|
||||
}
|
||||
return b
|
||||
.replace('[', '.').replace(']', '')
|
||||
.split('.')
|
||||
.reduce(
|
||||
function (obj, property) {
|
||||
return obj[property]
|
||||
}, a
|
||||
)
|
||||
}
|
||||
|
||||
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++) {
|
||||
arr.push(n)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
function orderBy (arr, funcs, dirs) {
|
||||
return arr.sort((a, b) => {
|
||||
for (let i = 0; i < funcs.length; i++) {
|
||||
const comp = funcs[i]
|
||||
const ca = comp(a)
|
||||
const cb = comp(b)
|
||||
const desc = dirs[i] === false || dirs[i] === 'desc'
|
||||
if (ca > cb) {
|
||||
return desc ? -1 : 1
|
||||
}
|
||||
if (ca < cb) {
|
||||
return desc ? 1 : -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
function clone (a) {
|
||||
return JSON.parse(JSON.stringify(a, function (key, value) {
|
||||
if (typeof value === 'function') {
|
||||
return value.toString()
|
||||
}
|
||||
return value
|
||||
}))
|
||||
}
|
||||
|
||||
// ########################################################################
|
||||
// Helpers
|
||||
// ########################################################################
|
||||
|
||||
function isArray (a) {
|
||||
return Array.isArray(a)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
export default {
|
||||
get,
|
||||
takeRight,
|
||||
last,
|
||||
orderBy,
|
||||
range,
|
||||
clone,
|
||||
remove,
|
||||
getFirstDefined
|
||||
}
|
||||
|
||||
function remove (a, b) {
|
||||
return a.filter(function (o, i) {
|
||||
var r = b(o)
|
||||
if (r) {
|
||||
a.splice(i, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function get (a, b) {
|
||||
if (isArray(b)) {
|
||||
b = b.join('.')
|
||||
}
|
||||
return b
|
||||
.replace('[', '.').replace(']', '')
|
||||
.split('.')
|
||||
.reduce(
|
||||
function (obj, property) {
|
||||
return obj[property]
|
||||
}, a
|
||||
)
|
||||
}
|
||||
|
||||
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++) {
|
||||
arr.push(n)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
function orderBy (arr, funcs, dirs) {
|
||||
return arr.sort((a, b) => {
|
||||
for (let i = 0; i < funcs.length; i++) {
|
||||
const comp = funcs[i]
|
||||
const ca = comp(a)
|
||||
const cb = comp(b)
|
||||
const desc = dirs[i] === false || dirs[i] === 'desc'
|
||||
if (ca > cb) {
|
||||
return desc ? -1 : 1
|
||||
}
|
||||
if (ca < cb) {
|
||||
return desc ? 1 : -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
function clone (a) {
|
||||
return JSON.parse(JSON.stringify(a, function (key, value) {
|
||||
if (typeof value === 'function') {
|
||||
return value.toString()
|
||||
}
|
||||
return value
|
||||
}))
|
||||
}
|
||||
|
||||
// ########################################################################
|
||||
// Helpers
|
||||
// ########################################################################
|
||||
|
||||
function isArray (a) {
|
||||
return Array.isArray(a)
|
||||
}
|
||||
|
||||
function getFirstDefined (...args) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (typeof args[i] !== 'undefined') {
|
||||
return args[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user