src/components/table/index.js
import Format from '../../utils/format'
import PropTypes from 'prop-types'
import _ from 'lodash'
import React from 'react'
class Table extends React.Component {
static contextTypes = {
modal: PropTypes.object,
router: PropTypes.object,
tasks: PropTypes.object
}
static propTypes = {
columns: PropTypes.array,
handler: PropTypes.func,
link: PropTypes.func,
modal: PropTypes.any,
records: PropTypes.array,
recordTasks: PropTypes.array,
rowClass: PropTypes.func,
selectable: PropTypes.bool,
selected: PropTypes.array,
selectAll: PropTypes.bool,
sort: PropTypes.object,
onSelect: PropTypes.func,
onSelectAll: PropTypes.func,
onSort: PropTypes.func
}
static defaultProps = {
onSelect: (id) => {},
onSelectAll: () => {}
}
_handleResize: any = _.debounce(this._handleResize, 100)
head: any
state = {
widths: []
}
render(){
const { columns, link, records, recordTasks, selectable, selected, selectAll, sort } = this.props
return (
<div className="reframe-table">
<table className="reframe-table-pinned">
<thead>
<tr>
{ selectable &&
<td className="reframe-table-check-cell mobile" onClick={ this._handleSelectAll.bind(this) }>
{ selectAll ? <i className="fa fa-fw fa-check-circle" /> : <i className="fa fa-fw fa-circle-o" /> }
</td>
}
{ columns.filter(column => column.visible !== false).map((column, columnIndex) => (
<td key={`header-${columnIndex}`} className={ this._getHeaderClass(column) } style={ this._getHeadStyle(columnIndex + (selectable ? 1 : 0)) } onClick={ this._handleSort.bind(this, column) }>
{ column.label }
{ sort && (column.key === sort.key || column.sort === sort.key) &&
(sort.order === 'asc' ? <i className="fa fa-fw fa-chevron-up" /> : <i className="fa fa-fw fa-chevron-down" />)
}
</td>
))}
{ (link || recordTasks) &&
<td className="reframe-table-head-cell mobile collapsing next" style={ this._getHeadStyle() } />
}
</tr>
</thead>
</table>
<table className="reframe-table-data">
<thead>
<tr ref={ (node) => this.head = node }>
{ selectable &&
<td className="reframe-table-check-cell mobile" onClick={ this._handleSelectAll.bind(this) }>
{ selectAll ? <i className="fa fa-fw fa-check-circle" /> : <i className="fa fa-fw fa-circle-o" /> }
</td>
}
{ columns.filter(column => column.visible !== false).map((column, columnIndex) => (
<td key={`header-${columnIndex}`} className={ this._getHeaderClass(column) } style={ this._getHeadStyle(columnIndex + (selectable ? 1 : 0)) } onClick={ this._handleSort.bind(this, column) }>
{ column.label }
{ sort && (column.key === sort.key || column.sort === sort.key) &&
(sort.order === 'asc' ? <i className="fa fa-fw fa-chevron-up" /> : <i className="fa fa-fw fa-chevron-down" />)
}
</td>
))}
{ (link || recordTasks) &&
<td className="reframe-table-head-cell mobile collapsing next" style={ this._getHeadStyle() } />
}
</tr>
</thead>
<tbody>
{ records.map((record, rowIndex) => (
<tr key={ `record_${rowIndex}` } className={ this._getBodyRowClass(record) }>
{ selectable &&
<td key={`cell_${rowIndex}_select`} className="reframe-table-check-cell mobile" onClick={ this._handleSelect.bind(this, record.id) }>
{ _.includes(selected, record.id) ? <i className="fa fa-fw fa-check-circle" /> : <i className="fa fa-fw fa-circle-o" /> }
</td>
}
{ columns.filter(column => column.visible !== false).map((column, columnIndex) => (
<td key={ `cell_${rowIndex}_${columnIndex}` } className={ this._getBodyClass(column) } onClick={ this._handleClick.bind(this, record, rowIndex) }>
<Format { ...record } format={ column.format } value={ _.get(record, column.key) } />
</td>
)) }
{ recordTasks &&
<td className="icon mobile collapsing centered" onClick={ this._handleTasks.bind(this, record.id) }>
<i className="fa fa-fw fa-ellipsis-v" />
</td>
}
{ link &&
<td className="reframe-table-body-cell icon mobile collapsing centered">
<i className="fa fa-fw fa-chevron-right" />
</td>
}
</tr>
))}
</tbody>
</table>
</div>
)
}
componentDidMount(){
setTimeout(() => this._handleResize(), 250)
window.addEventListener('resize', this._handleResize.bind(this))
}
componentDidUpdate(prevProps) {
const { columns } = this.props
if(!_.isEqual(prevProps.columns, columns)) this._handleResize()
}
componentWillUnmount() {
window.removeEventListener('resize', this._handleResize.bind(this))
}
_getHeaderClass(column) {
let classes = ['padded']
if(column.primary === true) classes.push('mobile')
if(column.format === 'check' || column.collapsing === true) classes.push('collapsing')
return classes.join(' ')
}
_getBodyClass(column) {
let classes = []
if(column.primary === true) classes.push('mobile')
if(column.format === 'check' || column.collapsing === true) classes.push('collapsing')
if(column.format === 'check' || column.centered === true) classes.push('centered')
if(column.format === 'currency') classes.push('right')
if(!_.isFunction(column.format) && !_.isElement(column.format)) classes.push('padded')
return classes.join(' ')
}
_getBodyRowClass(record) {
const { link, modal, handler, rowClass } = this.props
let classes = []
if(link || modal || handler) classes.push('reframe-table-link')
if(rowClass && _.isString(rowClass)) classes.push(rowClass)
if(rowClass && _.isFunction(rowClass)) classes.push(rowClass(record))
return classes.join(' ')
}
_getHeadStyle(index){
const { widths } = this.state
const width = (typeof index !== 'undefined') ? widths[index] : widths[widths.length - 1]
return width ? { width: `${width}px` } : {}
}
_handleClick(record, index) {
const { link, modal, handler } = this.props
if(link) return this._handleLink(record, index)
if(modal) return this._handleModal(record, index)
if(handler) return this._handleHandler(record, index)
}
_handleHandler(record, index) {
this.props.handler(record, index)
}
_handleLink(record, index) {
const { link } = this.props
const path = link(record)
this.context.router.push(path)
}
_handleModal(record, index) {
this.context.model.open(() => <this.props.modal record={ record } index={ index } />)
}
_handleResize() {
if(!this.head) return
const headerCells = Array.from(this.head.childNodes)
const widths = headerCells.map((cell, index) => cell.offsetWidth)
this.setState({ widths })
}
_handleSelect(id) {
this.props.onSelect(id)
}
_handleSelectAll() {
this.props.onSelectAll()
}
_handleSort(column) {
const key = column.sort || column.key
this.props.onSort(key)
}
_handleTasks(id) {
const { recordTasks } = this.props
const tasks = recordTasks.map(task => ({
...task,
handler: task.handler ? () => task.handler(id) : null,
modal: task.modal ? () => <task.modal id={ id } /> : null,
request: task.request ? task.request(id): null
}))
this.context.tasks.open(tasks)
}
}
export default Table