Compare commits

...
21 Commits
Author SHA1 Message Date
Tanner Linsley 2abcc86954 3.1.2 2016-11-02 15:20:48 -06:00
Tanner Linsley 610972d99e Pagination Style fixes 2016-11-02 15:20:43 -06:00
Tanner Linsley c99796342b 3.1.1 2016-11-02 15:07:27 -06:00
Tanner Linsley 1b4d8bd45f Fix pagination not showing with 0 pagesLength 2016-11-02 15:07:20 -06:00
Tanner Linsley e922a43741 No example/dist 2016-11-02 13:08:20 -06:00
Tanner Linsley 75317c2251 Example Update 2016-11-02 13:07:05 -06:00
Tanner Linsley e531ef1f64 Readme Updates 2016-11-02 12:12:29 -06:00
Tanner Linsley 598d33410a 3.1.0 2016-11-02 12:09:57 -06:00
Tanner Linsley 57ac1f142d Pagination Features
- Page Jumping
- Page Size Changing
- Updated Examples
2016-11-02 12:09:52 -06:00
Tanner Linsley e181f8e3d1 Readme Update 2016-11-01 14:44:58 -06:00
Tanner Linsley 5e19459eba Readme updates 2016-10-31 12:20:03 -06:00
Tanner Linsley cad0ac49cb Readme updates 2016-10-31 12:17:40 -06:00
Tanner Linsley b5d17cdbce Readme Update 2016-10-31 11:12:48 -06:00
Tanner Linsley a5d9e15b5d Readme update 2016-10-31 11:11:01 -06:00
Tanner LinsleyandGitHub c9cf436576 Update async.js 2016-10-31 10:39:28 -06:00
Tanner Linsley ece0ddc8a4 Readme Updates 2016-10-31 02:00:41 -06:00
Tanner Linsley dbc90c2e63 Readme updates 2016-10-31 01:55:32 -06:00
Tanner Linsley 2b0e1eb8a7 Readme Updates 2016-10-31 01:53:54 -06:00
Tanner Linsley 3d0f38f2c5 3.0.0 2016-10-31 01:49:43 -06:00
Tanner Linsley 516b88468e Major changes for server-side data 2016-10-31 01:49:43 -06:00
Alex SimoesandTanner Linsley 07cd241ced Use curly brackets here instead of square brackets (#7) 2016-10-27 19:09:58 -06:00
20 changed files with 904 additions and 59744 deletions
+1
View File
@@ -3,3 +3,4 @@ lib/
react-table.js
react-table.css
*.log
example/dist/
+59 -48
View File
@@ -8,12 +8,11 @@ A fast, lightweight, opinionated table and datagrid built on React
## 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 data
- Lightweight at 4kb (and just 2kb more for styles)
- Fully customizable JSX and callbacks for everything
- Supports both Client-side & Server-side pagination and sorting
- Minimal design & easily themeable
- ["Why I wrote React Table and the problems it has solved for Nozzle.io"](https://medium.com/@tannerlinsley/why-i-wrote-react-table-and-the-problems-it-has-solved-for-nozzle-others-445c4e93d4a8#.axza4ixba) by Tanner Linsley
## [Demo](http://react-table.zabapps.com)
@@ -53,27 +52,28 @@ const data = [{
const columns = [{
header: 'Name',
accessor: 'name',
accessor: 'name' // String-based value accessors !
}, {
header: 'Age',
accessor: 'age'
accessor: 'age',
render: props => <span className='number'>props.value</span> // Custom cell components!
}, {
header: 'Friend Name',
accessor: d => d.friend.name
accessor: d => d.friend.name // Custom value accessors!
}, {
header: 'Friend Age',
header: props => <span>Friend Age</span>, // Custom header components!
accessor: 'friend.age'
}]
<ReactTable
data=[data]
data={data}
columns={columns}
/>
```
<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.
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 as you change any props. [Server-side data](#server-side-data) is also supported!
<a name="default-props"></a>
@@ -82,8 +82,15 @@ These are the default props for the main react component `<ReactTable />`
```javascript
{
// General
pageSize: 20,
loading: false, // Whether to show the loading overlay or not
pageSize: 20, // The default page size (this can be changed by the user if `showPageSizeOptions` is enabled)
minRows: 0, // Ensure this many rows are always rendered, regardless of rows on page
showPagination: true, // Shows or hides the pagination component
showPageSizeOptions: true, // Enables the user to change the page size
pageSizeOptions: [5, 10, 20, 25, 50, 100], // The available page size options
// Callbacks
onChange: (state, instance) => null, // Anytime the internal state of the table changes, this will fire
// Text
previousText: 'Previous',
@@ -134,7 +141,7 @@ Or just define them on the component per-instance
<a name="columns"></a>
## Columns
Every React-Table instance requires a `columns` prop, which is an array of objects containing the following properties
`<ReactTable/>` requires a `columns` prop, which is an array of objects with the following properties
```javascript
[{
@@ -142,8 +149,8 @@ Every React-Table instance requires a `columns` prop, which is an array of objec
accessor: 'propertyName' or Accessor eg. (row) => row.propertyName,
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,
sort: 'asc' or 'desc', // used to determine the column sorting on init
show: true, // can be used to hide a column
minWidth: Number // Allows the column to flex above this minimum amount
// Cell Options
@@ -196,47 +203,45 @@ 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. Instead of passing the `data` prop an array, you provide a function instead.
If you want to handle pagination, and sorting on the server, `react-table` makes it easy on you.
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.
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 at `compomentDidMount` and 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={(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!
...
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)
loading={this.state.loading}
manual // informs React Table that you'll be handling sorting and pagination server-side
onChange={(state, instance) => {
// show the loading overlay
this.setState({loading: true})
// fetch your data
Axios.post('mysite.com/data', {
page: state.page,
pageSize: state.pageSize,
sorting: state.sorting
})
.then((res) => {
// Update react-table
this.setState({
data: res.data.rows,
pages: res.data.pages,
loading: false
})
})
}}
/>
```
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!
@@ -290,3 +295,9 @@ If you would like to preview your changes, you can link and utilize the example
- `npm watch`
- Make changes to the example in `src/screens/index.js` if needed
- View changes at `localhost:8000`
## Used By
<a href='https://nozzle.io'>
<img src='https://nozzle.io/img/logo-blue.png' alt='Nozzle Logo' style='width:300px;'/>
</a>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

-171
View File
File diff suppressed because one or more lines are too long
-59142
View File
File diff suppressed because one or more lines are too long
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>React-Table Demo</title>
<link rel="stylesheet" href="/app.css" charset="utf-8">
</head>
<body>
<div id="app"></div>
<script src="/app.js"></script>
<script async defer src="https://buttons.github.io/buttons.js"></script>
</body>
</html>
+3 -1
View File
@@ -10,7 +10,8 @@
},
"scripts": {
"watch": "jumpsuit watch",
"build": "jumpsuit build"
"build": "jumpsuit build",
"deploy": "jumpsuit build && zab deploy"
},
"devDependencies": {
"nib": "^1.1.0",
@@ -21,6 +22,7 @@
"jumpsuit": "^0.7.5",
"lodash": "^4.16.4",
"namor": "^0.3.0",
"react-syntax-highlighter": "^3.0.0",
"react-table": "^2.0.0"
}
}
+16 -3
View File
@@ -1,4 +1,17 @@
import { Render } from 'jumpsuit'
import App from 'screens/index'
import { Render, Router, Route, IndexRoute } from 'jumpsuit'
//
import Layout from 'components/layout'
import Simple from 'screens/simple'
import ServerSide from 'screens/serverSide'
Render(null, <App />)
Render(null, (
<Router>
<Route path='/' component={Layout}>
<IndexRoute component={Simple} />
<Route path='simple' component={Simple} />
<Route path='server-side' component={ServerSide} />
</Route>
</Router>
), {
useHash: false
})
+39 -6
View File
@@ -11,7 +11,7 @@ global-reset()
// vendor styles
// -----------------------------------------------------------------------------
@import '../node_modules/react-table/react-table.css'
@import '../../src/index'
// -----------------------------------------------------------------------------
// variables
@@ -34,7 +34,6 @@ body
background: white
font-family: $fnt-open-sans
font-weight: 300
padding-bottom: 50px
h1
font-size: 2.5em
@@ -50,6 +49,7 @@ strong
.logo
width: 400px
max-width: 100%
.container
display: flex
@@ -63,11 +63,34 @@ strong
font-size: 20px
padding: 10px
.table-wrap
width: 700px
.viewport
width:100%
.table-wrap
width: 90%
margin: auto
padding: 10px
border-radius: 5px
box-shadow: 0 0 20px 0 alpha(black, .2)
.menu
display:block
margin: 0 10px 20px
ul
display:block
li
display:inline-block
a
display:block
padding: 10px
border-radius: 5px
box-shadow: 0 0 20px 0 alpha(black, .2)
margin: 5px
background: alpha(black, .7)
color: white
border-radius: 3px
transition: all .2s ease-out
&.active
&:hover
background: alpha(black, .9)
.ReactTable
thead
@@ -77,3 +100,13 @@ strong
box-shadow:inset 0 3px 0 0 alpha(black, .6)
&.-sort-desc
box-shadow:inset 0 -3px 0 0 alpha(black, .6)
pre
display:block
font-family: monospace
font-size: 15px
line-height: 20px
border-radius: 5px
margin: 20px auto
max-width: 90%
padding: 0 20px !important
+1
View File
@@ -2,6 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>React-Table Demo</title>
<link rel="stylesheet" href="/app.css" charset="utf-8">
</head>
+11
View File
@@ -0,0 +1,11 @@
import { Component } from 'jumpsuit'
import SyntaxHighlighter from 'react-syntax-highlighter'
import atomOneDark from '../../node_modules/react-syntax-highlighter/dist/styles/atom-one-dark'
export default Component({
render () {
return (
<SyntaxHighlighter language='javascript' style={atomOneDark}>{this.props.children}</SyntaxHighlighter>
)
}
})
@@ -1,43 +1,13 @@
import { Component } from 'jumpsuit'
import _ from 'lodash'
import namor from 'namor'
import ReactTable from 'react-table'
import { Component, Link } from 'jumpsuit'
export default Component({
render () {
const data = _.map(_.range(5000), d => {
return {
firstName: namor.generate({ words: 1, numLen: 0 }),
lastName: namor.generate({ words: 1, numLen: 0 }),
age: Math.floor(Math.random() * 30)
}
})
const 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'
}]
}]
return (
<div className='container'>
<div style={{textAlign: 'center'}}>
<h1>
<span style={{position: 'absolute', textIndent: '-9999em'}}>
react-table <strong>demo</strong>
react-table
</span>
<img src='/Banner.png' className='logo' />
</h1>
@@ -66,15 +36,22 @@ export default Component({
<br />
<br />
</div>
<div className='table-wrap'>
<ReactTable
data={data}
columns={columns}
/>
<div className='menu'>
<ul>
<li>
<Link to='/simple' activeClassName='active'>
Simple
</Link>
</li>
<li>
<Link to='/server-side' activeClassName='active'>
Server-Side
</Link>
</li>
</ul>
</div>
<div style={{textAlign: 'center'}}>
<br />
<em>Tip: Hold shift when sorting to multi-sort!</em>
<div className='viewport'>
{this.props.children}
</div>
</div>
)
+171
View File
@@ -0,0 +1,171 @@
import { Component } from 'jumpsuit'
import _ from 'lodash'
import namor from 'namor'
import CodeHighlight from 'components/codeHighlight'
import ReactTable from '../../../lib/index.js'
// Let's mock some data to play around with
const rawData = _.map(_.range(3424), 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>
<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={10}
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>
<CodeHighlight>{getCode()}</CodeHighlight>
</div>
)
}
})
function getCode () {
return `
import ReactTable from 'react-table'
import Axios from 'axios'
export default React.creatClass({
getInitialState () {
// To handle our data server-side, we need to keep track of our table state
return {
data: [],
pages: null,
loading: true
}
},
fetchData (state, instance) {
// Whenever the table model changes (sorting, pagination, etc), 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 loading notice, or show you're own loading bar if you want.
this.setState({loading: true})
// Request the data from a server however you want! Be sure to send the bits of the table model that it may neeed.
Axios.post('mysite.com/data', {
pageSize: state.pageSize,
page: state.page,
sorting: state.sorting
})
.then((res) => {
// Now update your state!
this.setState({
data: res.rows,
pages: res.pages,
loading: false
})
})
},
render () {
const 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'
}]
}]
return (
<ReactTable
columns={columns}
manual // This forces table not to paginate or sort automatically, so we can handle things server-side
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
/>
)
}
})
`
}
+103
View File
@@ -0,0 +1,103 @@
import { Component } from 'jumpsuit'
import _ from 'lodash'
import namor from 'namor'
import CodeHighlight from 'components/codeHighlight'
import ReactTable from '../../../lib/index.js'
export default Component({
render () {
const data = _.map(_.range(5553), d => {
return {
firstName: namor.generate({ words: 1, numLen: 0 }),
lastName: namor.generate({ words: 1, numLen: 0 }),
age: Math.floor(Math.random() * 30)
}
})
const 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'
}]
}]
return (
<div>
<div className='table-wrap'>
<ReactTable
data={data}
columns={columns}
pageSize={10}
/>
</div>
<div style={{textAlign: 'center'}}>
<br />
<em>Tip: Hold shift when sorting to multi-sort!</em>
</div>
<CodeHighlight>{getCode()}</CodeHighlight>
</div>
)
}
})
function getCode () {
return `
import ReactTable from 'react-table'
// To help us mock some data
import namor from 'namor'
import _ from 'lodash'
export default () => {
// Mock some data
const data = _.map(_.range(5553), d => {
return {
firstName: namor.generate({ words: 1, numLen: 0 }),
lastName: namor.generate({ words: 1, numLen: 0 }),
age: Math.floor(Math.random() * 30)
}
})
// Create some column definitions
const 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'
}]
}]
// Display your table!
return (
<ReactTable
data={data}
columns={columns}
pageSize={10}
/>
)
})
`
}
+5 -1
View File
@@ -1,4 +1,8 @@
{
"name": "react-table",
"root": "dist"
"root": "dist",
"routes": [{
"src": "/**",
"dest": "index.html"
}]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "react-table",
"version": "2.2.7",
"version": "3.1.2",
"description": "A fast, lightweight, opinionated table and datagrid built on React",
"license": "MIT",
"homepage": "https://github.com/tannerlinsley/react-table#readme",
+206 -308
View File
@@ -1,21 +1,21 @@
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>
)
import Pagination from './pagination'
export const ReactTableDefaults = {
// General
data: [],
loading: false,
pageSize: 20,
showPagination: true,
showPageSizeOptions: true,
pageSizeOptions: [5, 10, 20, 25, 50, 100],
showPageJump: true,
// Callbacks
onChange: () => null,
// Classes
className: '-striped -highlight',
tableClassName: '',
@@ -37,9 +37,6 @@ export const ReactTableDefaults = {
thStyle: {},
tdStyle: {},
paginationStyle: {},
//
pageSize: 20,
minRows: 0,
// Global Column Defaults
column: {
sortable: true,
@@ -62,12 +59,24 @@ export const ReactTableDefaults = {
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>,
thComponent: (props) => {
const {toggleSort, ...rest} = props
return (
<th {...rest} onClick={e => {
toggleSort && toggleSort(e)
}}>{props.children}</th>
)
},
tdComponent: (props) => <td {...props}>{props.children}</td>,
previousComponent: null,
nextComponent: null,
// Unlisted
data: []
loadingComponent: props => (
<div className={classnames('-loading', {'-active': props.loading})}>
<div className='-loading-inner'>
{props.loadingText}
</div>
</div>
)
}
export default React.createClass({
@@ -76,91 +85,32 @@ export default React.createClass({
},
getInitialState () {
return {
page: 0,
sorting: false
}
},
componentWillMount () {
this.update(this.props)
componentDidMount () {
this.fireOnChange()
},
componentWillReceiveProps (nextProps) {
this.update(nextProps)
fireOnChange () {
this.props.onChange({
page: this.getPropOrState('page'),
pageSize: this.getStateOrProp('pageSize'),
pages: this.getPagesLength(),
sorting: this.getSorting()
}, this)
},
update (props) {
const resetState = {
loading: false,
page: 0,
pages: -1
// columns: {} for column hiding in the future
}
this.setState(resetState)
const newState = Object.assign({}, this.state, resetState)
this.isAsync = typeof props.data === 'function'
this.buildColumns(props, newState)
this.buildData(props, newState)
getPropOrState (key) {
return _.getFirstDefined(this.props[key], this.state[key])
},
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)
return dcol
}
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
}
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)
}
getStateOrProp (key) {
return _.getFirstDefined(this.state[key], this.props[key])
},
getInitSorting () {
const initSorting = this.decoratedColumns.filter(d => {
getInitSorting (columns) {
if (!columns) {
return []
}
const initSorting = columns.filter(d => {
return typeof d.sort !== 'undefined'
}).map(d => {
return {
@@ -170,104 +120,127 @@ 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, i) => {
const row = {
__original: d,
__index: i
}
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))
},
getPagesLength () {
return this.props.manual ? this.props.pages
: Math.ceil(this.props.data.length / this.getStateOrProp('pageSize'))
},
getMinRows () {
return _.getFirstDefined(this.props.minRows, this.props.pageSize)
},
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)
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 padRows = pagesLength > 1 ? _.range(this.props.pageSize - pageRows.length)
: this.props.minRows ? _.range(Math.max(this.props.minRows - pageRows.length, 0))
// 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)
// Normalize state
const currentPage = this.getPropOrState('page')
const pageSize = this.getStateOrProp('pageSize')
const pagesLength = this.getPagesLength()
// Pagination
const startRow = pageSize * currentPage
const endRow = startRow + pageSize
const pageRows = this.props.manual ? data : data.slice(startRow, endRow)
const minRows = this.getMinRows()
const padRows = pagesLength > 1 ? _.range(pageSize - pageRows.length)
: minRows ? _.range(Math.max(minRows - pageRows.length, 0))
: []
const canPrevious = this.state.page > 0
const canNext = this.state.page + 1 < pagesLength
const canPrevious = currentPage > 0
const canNext = currentPage + 1 < pagesLength
const TableComponent = this.props.tableComponent
const TheadComponent = this.props.theadComponent
@@ -275,9 +248,9 @@ export default React.createClass({
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
const PreviousComponent = this.props.previousComponent
const NextComponent = this.props.nextComponent
const LoadingComponent = this.props.loadingComponent
return (
<div
@@ -288,7 +261,7 @@ export default React.createClass({
className={classnames(this.props.tableClassName)}
style={this.props.tableStyle}
>
{this.hasHeaderGroups && (
{hasHeaderGroups && (
<TheadComponent
className={classnames(this.props.theadGroupClassName, '-headerGroups')}
style={this.props.theadStyle}
@@ -297,7 +270,7 @@ export default React.createClass({
className={this.props.trClassName}
style={this.props.trStyle}
>
{this.headerGroups.map((column, i) => {
{headerGroups.map((column, i) => {
return (
<ThComponent
key={i}
@@ -330,8 +303,8 @@ export default React.createClass({
className={this.props.trClassName}
style={this.props.trStyle}
>
{this.decoratedColumns.map((column, i) => {
const sort = this.state.sorting.find(d => d.id === column.id)
{decoratedColumns.map((column, i) => {
const sort = sorting.find(d => d.id === column.id)
const show = typeof column.show === 'function' ? column.show() : column.show
return (
<ThComponent
@@ -346,7 +319,7 @@ export default React.createClass({
}
)}
style={Object.assign({}, this.props.thStyle, column.headerStyle)}
onClick={(e) => {
toggleSort={(e) => {
column.sortable && this.sortColumn(column, e.shiftKey)
}}
>
@@ -385,7 +358,7 @@ export default React.createClass({
className={classnames(this.props.trClassName, this.props.trClassCallback(rowInfo))}
style={Object.assign({}, this.props.trStyle, this.props.trStyleCallback(rowInfo))}
>
{this.decoratedColumns.map((column, i2) => {
{decoratedColumns.map((column, i2) => {
const Cell = column.render
const show = typeof column.show === 'function' ? column.show() : column.show
return (
@@ -421,7 +394,7 @@ export default React.createClass({
className={classnames(this.props.trClassName, '-padRow')}
style={this.props.trStyle}
>
{this.decoratedColumns.map((column, i2) => {
{decoratedColumns.map((column, i2) => {
const show = typeof column.show === 'function' ? column.show() : column.show
return (
<TdComponent
@@ -443,42 +416,51 @@ export default React.createClass({
})}
</TbodyComponent>
</TableComponent>
{pagesLength > 1 && (
<div
className={classnames(this.props.paginationClassName, '-pagination')}
style={this.props.paginationStyle}
>
<div className='-left'>
<PreviousComponent
onClick={canPrevious && ((e) => this.previousPage(e))}
disabled={!canPrevious}
>
{this.props.previousText}
</PreviousComponent>
</div>
<div className='-center'>
Page {this.state.page + 1} of {pagesLength}
</div>
<div className='-right'>
<NextComponent
onClick={canNext && ((e) => this.nextPage(e))}
disabled={!canNext}
>
{this.props.nextText}
</NextComponent>
</div>
</div>
{this.props.showPagination && (
<Pagination
currentPage={currentPage}
pagesLength={pagesLength}
pageSize={pageSize}
showPageSizeOptions={this.props.showPageSizeOptions}
pageSizeOptions={this.props.pageSizeOptions}
showPageJump={this.props.showPageJump}
canPrevious={canPrevious}
canNext={canNext}
previousText={this.props.previousText}
nextText={this.props.nextText}
previousComponent={PreviousComponent}
nextComponent={NextComponent}
//
onChange={this.setPage}
onPageSizeChange={this.setPageSize}
/>
)}
<div className={classnames('-loading', {'-active': this.state.loading})}>
<div className='-loading-inner'>
{this.props.loadingText}
</div>
</div>
<LoadingComponent {...this.props} />
</div>
)
},
// User actions
setPage (page) {
this.setState({
page
}, () => {
this.fireOnChange()
})
},
setPageSize (pageSize) {
const currentPageSize = this.getStateOrProp('pageSize')
const currentPage = this.getPropOrState('page')
const currentRow = currentPageSize * currentPage
const page = Math.floor(currentRow / pageSize)
this.setState({
pageSize,
page
}, () => {
this.fireOnChange()
})
},
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) {
@@ -510,95 +492,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)
}
+55 -10
View File
@@ -120,10 +120,10 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
.-pagination
width:100%
display:flex
margin-top:5px
justify-content: space-between
align-items: center
flex-wrap: wrap
padding-top:3px
.-btn
appearance:none
@@ -147,21 +147,39 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
background: alpha(black, .3)
color: white
.-left
.-previous
.-center
.-right
flex: 1
// min-width:150px
margin-bottom:5px
.-next
margin:3px 0
.-left
.-right
.-previous
.-next
flex: 1
text-align: center
.-center
flex: 1.5
text-align:center
padding: 0 5px
margin-bottom:0
display: flex
flex-direction: row
flex-wrap: wrap
align-items: center
justify-content: space-around
.-pageInfo
display: inline-block
margin: 0 10px 3px
white-space: nowrap
.-pageJump
display:inline-block
input
width: 70px
text-align:center
.-pageSizeOptions
margin:0 10px 3px
.-loading
@@ -194,3 +212,30 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
pointer-events: all
> div
transform: translateY(50%)
input
select
appearance: none
border: 1px solid rgba(0,0,0,0.1)
background: white
padding: 5px 7px
font-size: inherit
border-radius: 3px
font-weight: normal
outline:none
.select-wrap
position:relative
display:inline-block
select
padding: 5px 15px 5px 7px
min-width:100px
&:after
content: ''
position: absolute
right: 8px
top: 50%
transform: translate(0, -50%)
border-color: #999 transparent transparent
border-style: solid
border-width: 5px 5px 2.5px
+121
View File
@@ -0,0 +1,121 @@
import React from 'react'
import classnames from 'classnames'
//
// import _ from './utils'
const defaultButton = (props) => (
<button {...props} className='-btn'>{props.children}</button>
)
export default React.createClass({
getInitialState () {
return {
page: this.props.currentPage
}
},
componentWillReceiveProps (nextProps) {
this.setState({page: nextProps.currentPage})
},
getSafePage (page) {
return Math.min(Math.max(page, 0), this.props.pagesLength - 1)
},
changePage (page) {
page = this.getSafePage(page)
this.setState({page})
this.props.onChange(page)
},
applyPage (e) {
e && e.preventDefault()
const page = this.state.page
this.changePage(page === '' ? this.props.currentPage : page)
},
render () {
const {
currentPage,
pagesLength,
showPageSizeOptions,
pageSizeOptions,
pageSize,
showPageJump,
canPrevious,
canNext,
onPageSizeChange
} = this.props
const PreviousComponent = this.props.PreviousComponent || defaultButton
const NextComponent = this.props.NextComponent || defaultButton
return (
<div
className={classnames(this.props.paginationClassName, '-pagination')}
style={this.props.paginationStyle}
>
<div className='-previous'>
<PreviousComponent
onClick={(e) => {
if (!canPrevious) return
this.changePage(currentPage - 1)
}}
disabled={!canPrevious}
>
{this.props.previousText}
</PreviousComponent>
</div>
<div className='-center'>
<span className='-pageInfo'>
Page {showPageJump ? (
<form className='-pageJump'
onSubmit={this.applyPage}
>
<input
type={this.state.page === '' ? 'text' : 'number'}
onChange={e => {
const val = e.target.value
const page = val - 1
if (val === '') {
return this.setState({page: val})
}
this.setState({page: this.getSafePage(page)})
}}
value={this.state.page === '' ? '' : this.state.page + 1}
onBlur={this.applyPage}
/>
</form>
) : (
<span className='-currentPage'>{currentPage + 1}</span>
)} of <span className='-totalPages'>{pagesLength}</span>
</span>
{showPageSizeOptions && (
<span className='select-wrap -pageSizeOptions'>
<select
onChange={(e) => onPageSizeChange(Number(e.target.value))}
value={pageSize}
>
{pageSizeOptions.map((option, i) => {
return (
<option
key={i}
value={option}>
{option} rows
</option>
)
})}
</select>
</span>
)}
</div>
<div className='-next'>
<NextComponent
onClick={(e) => {
if (!canNext) return
this.changePage(currentPage + 1)
}}
disabled={!canNext}
>
{this.props.nextText}
</NextComponent>
</div>
</div>
)
}
})
+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]
}
}
}