Compare commits

..
46 Commits
Author SHA1 Message Date
Tanner Linsley 2fe97f921f 3.0.0 2016-10-31 01:44:13 -06:00
Tanner Linsley 9a0369ee33 Major changes for server-side data 2016-10-31 01:44:07 -06:00
Tanner Linsley 48e6d16d58 2.2.7 2016-10-27 16:39:04 -06:00
Tanner Linsley e4208e3b0f Allow column without accessor or id 2016-10-27 16:38:55 -06:00
Tanner Linsley e6d03673ff 2.2.6 2016-10-27 09:32:22 -06:00
Tanner Linsley c3fc84db01 Fixed Column Accessor bug 2016-10-27 09:32:15 -06:00
Tanner Linsley 3334796223 2.2.5 2016-10-27 09:20:21 -06:00
Tanner Linsley 92b70393fa Added rowValues as a prop & Readme Updates 2016-10-27 09:20:16 -06:00
Tanner Linsley efeabe3536 2.2.4 2016-10-27 08:59:34 -06:00
Tanner Linsley f018bac58f Fixed row value prop 2016-10-27 08:59:30 -06:00
Tanner Linsley a8f360a408 Remove Build Files 2016-10-26 19:39:18 -06:00
Tanner Linsley 82f3913034 2.2.3 2016-10-26 19:37:15 -06:00
Tanner Linsley cb3cc05a59 Fixed row references 2016-10-26 19:37:10 -06:00
Tanner Linsley e8146b5d14 2.2.2 2016-10-26 19:25:51 -06:00
Tanner Linsley eef59a559e Added stuff back to umd files array 2016-10-26 19:25:45 -06:00
Tanner Linsley 3b18e8c8c1 2.2.1 2016-10-26 19:18:51 -06:00
Tanner Linsley dd6b8af304 Ignore built files in git 2016-10-26 19:18:45 -06:00
Tanner Linsley f242d2f3f6 Fixed tbodystyle bug 2016-10-26 19:15:36 -06:00
Tanner Linsley 9dd0333571 Readme Update 2016-10-26 19:11:48 -06:00
Tanner Linsley 085616d858 2.2.0 2016-10-26 17:33:32 -06:00
Tanner Linsley 25ef0b2273 Styles, ClassNames & Row Style/ClassName callbacks 2016-10-26 17:33:26 -06:00
Tanner Linsley 1fef30311c 2.1.3 2016-10-26 00:43:52 -06:00
Tanner Linsley 5351f79293 Better warnings and more column accessor freedom 2016-10-26 00:43:42 -06:00
Tanner Linsley 0bb3f2421e Readme Updates 2016-10-26 00:23:18 -06:00
Tanner Linsley c392c90eb6 Readme Updates 2016-10-26 00:21:24 -06:00
Tanner Linsley fc5cc00fce Readme Updates 2016-10-26 00:20:40 -06:00
Tanner Linsley a8c8211ef8 Readme updates 2016-10-26 00:13:51 -06:00
Tanner Linsley 8bff135119 2.1.2 2016-10-25 23:37:05 -06:00
Tanner Linsley e930d834c6 Fixed index to point to original index, added viewIndex 2016-10-25 23:36:59 -06:00
Tanner Linsley 7700f6a1a0 Standard ignore lib 2016-10-25 17:07:09 -06:00
Tanner Linsley 7a941b880f 2.1.1 2016-10-25 17:01:42 -06:00
Tanner Linsley 2e143e8d27 Babel lib as main input 2016-10-25 17:01:34 -06:00
Tanner Linsley ee2345f43c Slack badge has a slash now 2016-10-24 09:40:14 -06:00
Tanner Linsley e3129a2c80 Readme Updates 2016-10-24 09:30:04 -06:00
Tanner Linsley 1f943ffe25 Added nib to devDeps 2016-10-24 09:11:12 -06:00
Tanner Linsley c854c6914d Updated Readme and Added npm-run-all to devDeps 2016-10-24 09:05:55 -06:00
Tanner Linsley 80cef0d4c6 Use Node 6 in Travis 2016-10-24 09:02:19 -06:00
Tanner Linsley 302eb3d951 Removed React from devDeps 2016-10-24 09:01:23 -06:00
Tanner Linsley 4af3637c0b Added Travis with standard testing 2016-10-24 08:59:22 -06:00
Tanner Linsley 4a18536da7 2.1.0 2016-10-24 08:53:31 -06:00
Tanner Linsley 850f659309 Component and Class overrides 2016-10-24 08:53:04 -06:00
Tanner Linsley 0409463052 Better example 2016-10-22 17:08:05 -06:00
Tanner LinsleyandGitHub 66afd8cded Update README.md 2016-10-22 14:59:18 -06:00
Tanner Linsley 5aa764de03 Artwork 2016-10-22 14:57:07 -06:00
Tanner LinsleyandGitHub 2af8f75de9 Update README.md 2016-10-22 14:12:02 -06:00
Tanner Linsley 5615c43b29 Readme Contribution Update 2016-10-22 12:09:42 -06:00
19 changed files with 1496 additions and 471 deletions
+3 -1
View File
@@ -1,3 +1,5 @@
node_modules/
lib/
react-table.js
react-table.css
*.log
+4
View File
@@ -0,0 +1,4 @@
language: node_js
node_js:
- "6"
script: npm test
+161 -88
View File
@@ -1,32 +1,40 @@
![react-table](https://github.com/tannerlinsley/react-table/raw/master/media/Banner.png)
# react-table
A fast, lightweight, opinionated table and datagrid built on React
[![react-table on Slack](https://img.shields.io/badge/slack-reactTable-blue.svg)](https://react-table-slack.herokuapp.com/)
[![Build Status](https://travis-ci.org/tannerlinsley/react-table.svg?branch=master)](https://travis-ci.org/tannerlinsley/react-table)
[![react-table on Slack](https://img.shields.io/badge/slack-react--table-blue.svg)](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`
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+18 -9
View File
@@ -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);
}
+730 -24
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -1,4 +1,5 @@
import { Render } from 'jumpsuit'
// import App from 'screens/async'
import App from 'screens/index'
Render(null, <App />)
+17 -8
View File
@@ -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

+134
View File
@@ -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>
)
}
})
+11 -5
View File
@@ -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>
)
BIN
View File
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
View File
@@ -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"
]
},
-1
View File
File diff suppressed because one or more lines are too long
-7
View File
File diff suppressed because one or more lines are too long
+297 -320
View File
@@ -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'
}}>&nbsp;</div>
</td>
})}
>&nbsp;</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)
}
+95
View File
@@ -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]
}
}
}