Skip to content

Commit

Permalink
Merge pull request #141 from /issues/140
Browse files Browse the repository at this point in the history
Added custom header-renderer support for FlexTable (via FlexColumn)
  • Loading branch information
bvaughn committed Mar 3, 2016
2 parents 51dc246 + cf1e077 commit 8e25894
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 47 deletions.
13 changes: 13 additions & 0 deletions docs/FlexColumn.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Describes the header and cell contents of a table column
| flexGrow | Number | | Flex grow style; defaults to 0 |
| flexShrink | Number | | Flex shrink style; defaults to 1 |
| headerClassName | String | | CSS class to apply to this column's header |
| headerRenderer | Function | | Optional callback responsible for rendering a column's header column. [Learn more](#headerrenderer) |
| label | String | | Header label for this column |
| maxWidth | Number | | Maximum width of column; this property will only be used if :flexGrow is greater than 0 |
| minWidth | Number | | Minimum width of column |
Expand Down Expand Up @@ -44,3 +45,15 @@ function (cellData: any, cellDataKey: string, rowData: any, rowIndex: number, co

A defdault `cellRenderer` is provided that displays an attribute as a simple string
You should override this default method if your data is some other type of object or requires custom formatting.

#### headerRenderer

Callback responsible for rendering a cell's header column.
It should implement the following signature:

```javascript
function ({ columnData: any, dataKey: string, disableSort: boolean, label: string, sortBy: string, sortDirection: SortDirection }): element
```

A defdault `headerRenderer` is provided that displays the column `label` along with a sort indicator if the column is sort-enabled and active.
You should override this default method if you want to customize the appearance of table columns.
58 changes: 56 additions & 2 deletions source/FlexTable/FlexColumn.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @flow */
import { Component, PropTypes } from 'react'
import React, { Component, PropTypes } from 'react'
import SortIndicator from './SortIndicator'

/**
* Default cell renderer that displays an attribute as a simple string
Expand Down Expand Up @@ -36,6 +37,40 @@ export function defaultCellDataGetter (
}
}

/**
* Default table header renderer.
*/
export function defaultHeaderRenderer ({
columnData,
dataKey,
disableSort,
label,
sortBy,
sortDirection
}) {
const showSortIndicator = sortBy === dataKey
const children = [
<div
className='FlexTable__headerTruncatedText'
key='label'
title={label}
>
{label}
</div>
]

if (showSortIndicator) {
children.push(
<SortIndicator
key='SortIndicator'
sortDirection={sortDirection}
/>
)
}

return children
}

/**
* Describes the header and cell contents of a table column.
*/
Expand All @@ -45,40 +80,59 @@ export default class Column extends Component {
cellDataGetter: defaultCellDataGetter,
cellRenderer: defaultCellRenderer,
flexGrow: 0,
flexShrink: 1
flexShrink: 1,
headerRenderer: defaultHeaderRenderer
}

static propTypes = {
/** Optional CSS class to apply to cell */
cellClassName: PropTypes.string,

/**
* Callback responsible for returning a cell's data, given its :dataKey
* (dataKey: string, rowData: any): any
*/
cellDataGetter: PropTypes.func,

/**
* Callback responsible for rendering a cell's contents.
* (cellData: any, cellDataKey: string, rowData: any, rowIndex: number, columnData: any): element
*/
cellRenderer: PropTypes.func,

/** Optional additional data passed to this column's :cellDataGetter */
columnData: PropTypes.object,

/** Uniquely identifies the row-data attribute correspnding to this cell */
dataKey: PropTypes.any.isRequired,

/** If sort is enabled for the table at large, disable it for this column */
disableSort: PropTypes.bool,

/** Flex grow style; defaults to 0 */
flexGrow: PropTypes.number,

/** Flex shrink style; defaults to 1 */
flexShrink: PropTypes.number,

/** Optional CSS class to apply to this column's header */
headerClassName: PropTypes.string,

/**
* Optional callback responsible for rendering a column header contents.
* ({ columnData: object, dataKey: string, disableSort: boolean, label: string, sortBy: string, sortDirection: string }): PropTypes.node
*/
headerRenderer: PropTypes.func.isRequired,

/** Header label for this column */
label: PropTypes.string,

/** Maximum width of column; this property will only be used if :flexGrow is > 0. */
maxWidth: PropTypes.number,

/** Minimum width of column. */
minWidth: PropTypes.number,

/** Flex basis (width) for this column; This value can grow or shrink based on :flexGrow and :flexShrink properties. */
width: PropTypes.number.isRequired
}
Expand Down
22 changes: 21 additions & 1 deletion source/FlexTable/FlexTable.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LabeledInput, InputRow } from '../demo/LabeledInput'
import AutoSizer from '../AutoSizer'
import FlexColumn from './FlexColumn'
import FlexTable, { SortDirection } from './FlexTable'
import SortIndicator from './SortIndicator'
import shouldPureComponentUpdate from 'react-pure-render/function'
import styles from './FlexTable.example.css'

Expand All @@ -32,6 +33,7 @@ export default class FlexTableExample extends Component {
}

this._getRowHeight = this._getRowHeight.bind(this)
this._headerRenderer = this._headerRenderer.bind(this)
this._noRowsRenderer = this._noRowsRenderer.bind(this)
this._onRowsCountChange = this._onRowsCountChange.bind(this)
this._onScrollToRowChange = this._onScrollToRowChange.bind(this)
Expand Down Expand Up @@ -162,9 +164,9 @@ export default class FlexTableExample extends Component {
width={50}
/>
<FlexColumn
label='Name'
dataKey='name'
disableSort={!this._isSortEnabled()}
headerRenderer={this._headerRenderer}
width={90}
/>
<FlexColumn
Expand Down Expand Up @@ -196,6 +198,24 @@ export default class FlexTableExample extends Component {
return this._getDatum(list, index).size
}

_headerRenderer ({
columnData,
dataKey,
disableSort,
label,
sortBy,
sortDirection
}) {
return (
<div>
Full Name
{sortBy === dataKey &&
<SortIndicator sortDirection={sortDirection}/>
}
</div>
)
}

_isSortEnabled () {
const { list } = this.props
const { rowsCount } = this.state
Expand Down
56 changes: 14 additions & 42 deletions source/FlexTable/FlexTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,7 @@ export default class FlexTable extends Component {

_createHeader (column, columnIndex) {
const { headerClassName, onHeaderClick, sort, sortBy, sortDirection } = this.props
const { dataKey, disableSort, label, columnData } = column.props
const showSortIndicator = sortBy === dataKey
const { dataKey, disableSort, headerRenderer, label, columnData } = column.props
const sortEnabled = !disableSort && sort

const classNames = cn(
Expand All @@ -327,22 +326,23 @@ export default class FlexTable extends Component {
onHeaderClick(dataKey, columnData)
}

const renderedHeader = headerRenderer({
columnData,
dataKey,
disableSort,
label,
sortBy,
sortDirection
})

return (
<div
key={`Header-Col${columnIndex}`}
className={classNames}
style={style}
onClick={onClick}
>
<div
className='FlexTable__headerTruncatedText'
title={label}
>
{label}
</div>
{showSortIndicator &&
<SortIndicator sortDirection={sortDirection} />
}
{renderedHeader}
</div>
)
}
Expand Down Expand Up @@ -409,8 +409,9 @@ export default class FlexTable extends Component {
_getRenderedHeaderRow () {
const { children, disableHeader } = this.props
const items = disableHeader ? [] : children
return React.Children.map(items, (column, columnIndex) =>
this._createHeader(column, columnIndex)

return React.Children.map(items, (column, index) =>
this._createHeader(column, index)
)
}

Expand All @@ -431,32 +432,3 @@ export default class FlexTable extends Component {
this.setState({ scrollbarWidth })
}
}

/**
* Displayed beside a header to indicate that a FlexTable is currently sorted by this column.
*/
export function SortIndicator ({ sortDirection }) {
const classNames = cn('FlexTable__sortableHeaderIcon', {
'FlexTable__sortableHeaderIcon--ASC': sortDirection === SortDirection.ASC,
'FlexTable__sortableHeaderIcon--DESC': sortDirection === SortDirection.DESC
})

return (
<svg
className={classNames}
width={18}
height={18}
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
{sortDirection === SortDirection.ASC
? <path d='M7 14l5-5 5 5z'/>
: <path d='M7 10l5 5 5-5z'/>
}
<path d='M0 0h24v24H0z' fill='none'/>
</svg>
)
}
SortIndicator.propTypes = {
sortDirection: PropTypes.oneOf([SortDirection.ASC, SortDirection.DESC])
}
73 changes: 72 additions & 1 deletion source/FlexTable/FlexTable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ describe('FlexTable', () => {
cellRenderer,
cellDataGetter,
className,
columnData = { data: 123 },
disableSort = false,
headerClassName,
headerHeight = 20,
headerRenderer = undefined,
height = 100,
noRowsRenderer = undefined,
onHeaderClick = undefined,
Expand Down Expand Up @@ -84,10 +86,11 @@ describe('FlexTable', () => {
<FlexColumn
label='Name'
dataKey='name'
columnData={ {data: 123} }
columnData={columnData}
width={50}
cellRenderer={cellRenderer}
cellDataGetter={cellDataGetter}
headerRenderer={headerRenderer}
disableSort={disableSort}
/>
<FlexColumn
Expand Down Expand Up @@ -311,6 +314,74 @@ describe('FlexTable', () => {
})
})

describe('headerRenderer', () => {
it('should render a custom header if one is provided', () => {
const columnData = { foo: 'foo', bar: 'bar' }
const headerRendererCalls = []
const table = renderTable({
columnData,
headerRenderer: (params) => {
headerRendererCalls.push(params)
return 'custom header'
},
sortBy: 'name',
sortDirection: SortDirection.ASC
})
const tableDOMNode = findDOMNode(table)
const nameColumn = tableDOMNode.querySelector('.FlexTable__headerColumn:first-of-type')

expect(nameColumn.textContent).toContain('custom header')
expect(headerRendererCalls.length).toEqual(1)

const headerRendererCall = headerRendererCalls[0]
expect(headerRendererCalls.length).toEqual(1)
expect(headerRendererCall.columnData).toEqual(columnData)
expect(headerRendererCall.dataKey).toEqual('name')
expect(headerRendererCall.disableSort).toEqual(false)
expect(headerRendererCall.label).toEqual('Name')
expect(headerRendererCall.sortBy).toEqual('name')
expect(headerRendererCall.sortDirection).toEqual(SortDirection.ASC)
})

it('should honor sort for custom headers', () => {
const sortCalls = []
const table = renderTable({
headerRenderer: (params) => 'custom header',
sort: (sortKey, sortDirection) => sortCalls.push([sortKey, sortDirection]),
sortBy: 'name',
sortDirection: SortDirection.ASC
})
const tableDOMNode = findDOMNode(table)
const nameColumn = tableDOMNode.querySelector('.FlexTable__headerColumn:first-of-type')

Simulate.click(nameColumn)

expect(sortCalls.length).toEqual(1)
const sortCall = sortCalls[0]
expect(sortCall[0]).toEqual('name')
expect(sortCall[1]).toEqual(SortDirection.DESC)
})

it('should honor :onHeaderClick for custom header', () => {
const columnData = { foo: 'foo', bar: 'bar' }
const onHeaderClickCalls = []
const table = renderTable({
columnData,
headerRenderer: (params) => 'custom header',
onHeaderClick: (dataKey, columnData) => onHeaderClickCalls.push([dataKey, columnData])
})
const tableDOMNode = findDOMNode(table)
const nameColumn = tableDOMNode.querySelector('.FlexTable__headerColumn:first-of-type')

Simulate.click(nameColumn)

expect(onHeaderClickCalls.length).toEqual(1)
const onHeaderClickCall = onHeaderClickCalls[0]
expect(onHeaderClickCall[0]).toEqual('name')
expect(onHeaderClickCall[1]).toEqual(columnData)
})
})

describe('noRowsRenderer', () => {
it('should call :noRowsRenderer if :rowsCount is 0', () => {
const table = renderTable({
Expand Down
Loading

0 comments on commit 8e25894

Please sign in to comment.