mirror of
https://github.com/gosticks/react-table.git
synced 2026-08-19 16:30:21 +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 |
+3
-1
@@ -1,3 +1,5 @@
|
||||
node_modules/
|
||||
|
||||
lib/
|
||||
react-table.js
|
||||
react-table.css
|
||||
*.log
|
||||
|
||||
@@ -9,26 +9,32 @@ 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
|
||||
- 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 yet. We chose to avoid the complex problems that come with it, and instead provide reliable and predictable pagination.
|
||||
|
||||
## [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'
|
||||
|
||||
@@ -63,75 +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 `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="default-props"></a>
|
||||
## Default Props
|
||||
These are the default props for the main react component `<ReactTable />`
|
||||
```javascript
|
||||
{
|
||||
// General
|
||||
pageSize: 20,
|
||||
minRows: 0, // Ensure this many rows are always rendered, regardless of rows on page
|
||||
|
||||
// Text
|
||||
previousText: 'Previous',
|
||||
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
|
||||
//
|
||||
pageSize: 20,
|
||||
minRows: 0, // Ensure this many rows are always rendered, regardless of rows on page
|
||||
// Global Column Defaults
|
||||
column: { // default properties for every column's model
|
||||
sortable: true,
|
||||
show: true
|
||||
},
|
||||
// Text
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next'
|
||||
// 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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -147,7 +120,7 @@ Object.assign(ReactTableDefaults, {
|
||||
})
|
||||
```
|
||||
|
||||
Or just define them on the component
|
||||
Or just define them on the component per-instance
|
||||
|
||||
```javascript
|
||||
<ReactTable
|
||||
@@ -157,30 +130,50 @@ Or just define them on the component
|
||||
/>
|
||||
```
|
||||
|
||||
## 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
|
||||
className: '', // Set the classname of the `th/td` element of the column
|
||||
innerClassName: '', // Set the classname of the `.th-inner/.td-inner` element of the column
|
||||
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
|
||||
@@ -199,6 +192,46 @@ 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
|
||||
|
||||
Vendored
+325
-244
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 />)
|
||||
|
||||
@@ -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',
|
||||
|
||||
-630
File diff suppressed because one or more lines are too long
+11
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-table",
|
||||
"version": "2.1.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",
|
||||
@@ -15,13 +15,20 @@
|
||||
"datagrid"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"files": [
|
||||
"lib/",
|
||||
"react-table.js",
|
||||
"react-table.css",
|
||||
"media/*.png"
|
||||
],
|
||||
"scripts": {
|
||||
"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:*",
|
||||
"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"
|
||||
"prepublish": "npm-run-all build:* && npm run umd",
|
||||
"postpublish": "git push --tags"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5"
|
||||
@@ -40,7 +47,7 @@
|
||||
"nib": "^1.1.2",
|
||||
"npm-run-all": "^3.1.1",
|
||||
"onchange": "^3.0.2",
|
||||
"standard": "8.0.0",
|
||||
"standard": "^8.0.0",
|
||||
"stylus": "^0.54.5",
|
||||
"uglifyify": "3.0.3"
|
||||
},
|
||||
@@ -52,6 +59,7 @@
|
||||
"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
+245
-295
@@ -1,35 +1,55 @@
|
||||
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 = {
|
||||
// State
|
||||
data: [],
|
||||
loading: false,
|
||||
pageSize: 20,
|
||||
minRows: 0,
|
||||
// Callbacks
|
||||
onChange: () => null,
|
||||
onPageChange: () => null,
|
||||
onSort: () => null,
|
||||
// Classes
|
||||
className: '-striped -highlight',
|
||||
tableClassName: '',
|
||||
theadClassName: '',
|
||||
tbodyClassName: '',
|
||||
trClassName: '',
|
||||
trClassCallback: d => null,
|
||||
thClassName: '',
|
||||
thGroupClassName: '',
|
||||
tdClassName: '',
|
||||
paginationClassName: '',
|
||||
//
|
||||
pageSize: 20,
|
||||
minRows: 0,
|
||||
// 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',
|
||||
@@ -43,9 +63,7 @@ export const ReactTableDefaults = {
|
||||
thComponent: (props) => <th {...props}>{props.children}</th>,
|
||||
tdComponent: (props) => <td {...props}>{props.children}</td>,
|
||||
previousComponent: null,
|
||||
nextComponent: null,
|
||||
// Unlisted
|
||||
data: []
|
||||
nextComponent: null
|
||||
}
|
||||
|
||||
export default React.createClass({
|
||||
@@ -54,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 {
|
||||
@@ -142,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))
|
||||
: []
|
||||
@@ -251,19 +223,35 @@ export default React.createClass({
|
||||
const NextComponent = this.props.nextComponent || defaultButton
|
||||
|
||||
return (
|
||||
<div className={classnames(this.props.className, 'ReactTable')}>
|
||||
<TableComponent className={classnames(this.props.tableClassName)}>
|
||||
{this.hasHeaderGroups && (
|
||||
<TheadComponent className={classnames(this.props.theadClassName, '-headerGroups')}>
|
||||
<TrComponent className={this.props.trClassName}>
|
||||
{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 (
|
||||
<ThComponent
|
||||
key={i}
|
||||
className={classnames(column.className)}
|
||||
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={classnames(column.innerClassName, '-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}
|
||||
@@ -277,31 +265,40 @@ export default React.createClass({
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
)}
|
||||
<TheadComponent className={classnames(this.props.theadClassName)}>
|
||||
<TrComponent className={this.props.trClassName}>
|
||||
{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 (
|
||||
<ThComponent
|
||||
key={i}
|
||||
className={classnames(
|
||||
column.className,
|
||||
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={classnames(column.innerClassName, '-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}
|
||||
@@ -314,33 +311,45 @@ export default React.createClass({
|
||||
})}
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
<TbodyComponent className={classnames(this.props.tbodyClassName)}>
|
||||
<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 (
|
||||
<TrComponent
|
||||
className={classnames(this.props.trClassName)}
|
||||
key={i}>
|
||||
{this.decoratedColumns.map((column, i2) => {
|
||||
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 (
|
||||
<TdComponent
|
||||
key={i2}
|
||||
className={classnames(column.className, {hidden: !show})}
|
||||
key={i2}>
|
||||
style={Object.assign({}, this.props.tdStyle, column.style)}
|
||||
>
|
||||
<div
|
||||
className={classnames(column.innerClassName, '-td-inner')}
|
||||
style={{
|
||||
width: column.width + 'px',
|
||||
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>
|
||||
</TdComponent>
|
||||
)
|
||||
@@ -351,20 +360,24 @@ export default React.createClass({
|
||||
{padRows.map((row, i) => {
|
||||
return (
|
||||
<TrComponent
|
||||
key={i}
|
||||
className={classnames(this.props.trClassName, '-padRow')}
|
||||
key={i}>
|
||||
{this.decoratedColumns.map((column, i2) => {
|
||||
style={this.props.trStyle}
|
||||
>
|
||||
{decoratedColumns.map((column, i2) => {
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
return (
|
||||
<TdComponent
|
||||
key={i2}
|
||||
className={classnames(column.className, {hidden: !show})}
|
||||
key={i2}>
|
||||
style={Object.assign({}, this.props.tdStyle, column.style)}
|
||||
>
|
||||
<div
|
||||
className={classnames(column.innerClassName, '-td-inner')}
|
||||
style={{
|
||||
width: column.width + 'px',
|
||||
style={Object.assign({}, column.innerStyle, {
|
||||
minWidth: column.minWidth + 'px'
|
||||
}}> </div>
|
||||
})}
|
||||
> </div>
|
||||
</TdComponent>
|
||||
)
|
||||
})}
|
||||
@@ -374,11 +387,15 @@ export default React.createClass({
|
||||
</TbodyComponent>
|
||||
</TableComponent>
|
||||
{pagesLength > 1 && (
|
||||
<div className={classnames(this.props.paginationClassName, '-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>
|
||||
@@ -388,13 +405,14 @@ 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.loadingText}
|
||||
</div>
|
||||
@@ -402,8 +420,24 @@ export default React.createClass({
|
||||
</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) {
|
||||
@@ -435,95 +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