From cf1e0773531a255449f25049430ec3ee60b04791 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 2 Mar 2016 21:01:06 -0800 Subject: [PATCH] Added custom header-renderer support for FlexTable (via FlexColumn) --- docs/FlexColumn.md | 13 +++++ source/FlexTable/FlexColumn.js | 58 ++++++++++++++++++++- source/FlexTable/FlexTable.example.js | 22 +++++++- source/FlexTable/FlexTable.js | 56 +++++--------------- source/FlexTable/FlexTable.test.js | 73 ++++++++++++++++++++++++++- source/FlexTable/SortIndicator.js | 32 ++++++++++++ source/FlexTable/index.js | 3 +- 7 files changed, 210 insertions(+), 47 deletions(-) create mode 100644 source/FlexTable/SortIndicator.js diff --git a/docs/FlexColumn.md b/docs/FlexColumn.md index c5ecdd998..4cc337fcb 100644 --- a/docs/FlexColumn.md +++ b/docs/FlexColumn.md @@ -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 | @@ -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. diff --git a/source/FlexTable/FlexColumn.js b/source/FlexTable/FlexColumn.js index 6efe6ccb0..2f997e877 100644 --- a/source/FlexTable/FlexColumn.js +++ b/source/FlexTable/FlexColumn.js @@ -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 @@ -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 = [ +
+ {label} +
+ ] + + if (showSortIndicator) { + children.push( + + ) + } + + return children +} + /** * Describes the header and cell contents of a table column. */ @@ -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 } diff --git a/source/FlexTable/FlexTable.example.js b/source/FlexTable/FlexTable.example.js index aa290df62..15ece6f16 100644 --- a/source/FlexTable/FlexTable.example.js +++ b/source/FlexTable/FlexTable.example.js @@ -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' @@ -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) @@ -162,9 +164,9 @@ export default class FlexTableExample extends Component { width={50} /> + Full Name + {sortBy === dataKey && + + } + + ) + } + _isSortEnabled () { const { list } = this.props const { rowsCount } = this.state diff --git a/source/FlexTable/FlexTable.js b/source/FlexTable/FlexTable.js index a8000ec00..3dcaa2758 100644 --- a/source/FlexTable/FlexTable.js +++ b/source/FlexTable/FlexTable.js @@ -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( @@ -327,6 +326,15 @@ export default class FlexTable extends Component { onHeaderClick(dataKey, columnData) } + const renderedHeader = headerRenderer({ + columnData, + dataKey, + disableSort, + label, + sortBy, + sortDirection + }) + return (
-
- {label} -
- {showSortIndicator && - - } + {renderedHeader}
) } @@ -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) ) } @@ -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 ( - - {sortDirection === SortDirection.ASC - ? - : - } - - - ) -} -SortIndicator.propTypes = { - sortDirection: PropTypes.oneOf([SortDirection.ASC, SortDirection.DESC]) -} diff --git a/source/FlexTable/FlexTable.test.js b/source/FlexTable/FlexTable.test.js index bcbb7a65f..6f28a00ed 100644 --- a/source/FlexTable/FlexTable.test.js +++ b/source/FlexTable/FlexTable.test.js @@ -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, @@ -84,10 +86,11 @@ 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({ diff --git a/source/FlexTable/SortIndicator.js b/source/FlexTable/SortIndicator.js new file mode 100644 index 000000000..0b4a898af --- /dev/null +++ b/source/FlexTable/SortIndicator.js @@ -0,0 +1,32 @@ +import React, { PropTypes } from 'react' +import cn from 'classnames' +import { SortDirection } from './FlexTable' + +/** + * Displayed beside a header to indicate that a FlexTable is currently sorted by this column. + */ +export default function SortIndicator ({ sortDirection }) { + const classNames = cn('FlexTable__sortableHeaderIcon', { + 'FlexTable__sortableHeaderIcon--ASC': sortDirection === SortDirection.ASC, + 'FlexTable__sortableHeaderIcon--DESC': sortDirection === SortDirection.DESC + }) + + return ( + + {sortDirection === SortDirection.ASC + ? + : + } + + + ) +} +SortIndicator.propTypes = { + sortDirection: PropTypes.oneOf([SortDirection.ASC, SortDirection.DESC]) +} diff --git a/source/FlexTable/index.js b/source/FlexTable/index.js index b6c5780f9..6a4d1e9ed 100644 --- a/source/FlexTable/index.js +++ b/source/FlexTable/index.js @@ -1,4 +1,5 @@ /* @flow */ export default from './FlexTable' -export FlexTable, { SortDirection, SortIndicator } from './FlexTable' +export FlexTable, { SortDirection } from './FlexTable' export FlexColumn from './FlexColumn' +export SortIndicator from './SortIndicator'