From 795995eea902799923b1d76ee59b7a4d1137e3c1 Mon Sep 17 00:00:00 2001 From: Wei Zhu Date: Mon, 23 Oct 2017 22:01:06 +0800 Subject: [PATCH 1/3] Allow overriding table elements --- examples/custom-component.html | 0 examples/custom-component.js | 54 +++++ src/BaseTable.js | 16 +- src/Table.js | 14 ++ src/TableCell.js | 17 +- src/TableHeader.js | 9 +- src/TableHeaderRow.js | 16 +- src/TableRow.js | 41 +++- tests/Table.spec.js | 49 ++++ tests/__snapshots__/Table.spec.js.snap | 307 +++++++++++++++++++++++++ 10 files changed, 498 insertions(+), 25 deletions(-) create mode 100644 examples/custom-component.html create mode 100644 examples/custom-component.js diff --git a/examples/custom-component.html b/examples/custom-component.html new file mode 100644 index 000000000..e69de29bb diff --git a/examples/custom-component.js b/examples/custom-component.js new file mode 100644 index 000000000..83ce2f741 --- /dev/null +++ b/examples/custom-component.js @@ -0,0 +1,54 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100 }, + { id: '123', title: 'title2', dataIndex: 'b', key: 'b', width: 100 }, + { title: 'title3', dataIndex: 'c', key: 'c', width: 200 }, + { + title: 'Operations', + dataIndex: '', + key: 'd', + render() { + return Operations; + }, + }, +]; + +const data = [ + { a: '123', key: '1' }, + { a: 'cdd', b: 'edd', key: '2' }, + { a: '1333', c: 'eee', d: 2, key: '3' }, +]; + +const MyTable = props => ; +const HeaderWrapper = props => ; +const HeaderRow = props => ; +const HeaderCell = props => ; +const BodyRow = props => ; +const BodyCell = props =>
; +const BodyWrapper = props =>
; + +const components = { + table: MyTable, + header: { + wrapper: HeaderWrapper, + row: HeaderRow, + cell: HeaderCell, + }, + body: { + wrapper: BodyWrapper, + row: BodyRow, + cell: BodyCell, + }, +}; + +ReactDOM.render( +
+

Custom Component

+ + , + document.getElementById('__react-content') +); diff --git a/src/BaseTable.js b/src/BaseTable.js index 91d3cc867..98f4b2075 100644 --- a/src/BaseTable.js +++ b/src/BaseTable.js @@ -44,6 +44,7 @@ class BaseTable extends React.Component { onRowContextMenu, onRowMouseEnter, onRowMouseLeave, + components, } = table.props; const { getRowKey, fixed, expander } = this.props; @@ -103,6 +104,7 @@ class BaseTable extends React.Component { rowKey={key} ancestorKeys={ancestorKeys} ref={rowRef(record, i, indent)} + components={components} {...expandableRow} /> )} @@ -129,7 +131,7 @@ class BaseTable extends React.Component { } render() { - const { prefixCls, scroll, data, getBodyWrapper } = this.context.table.props; + const { prefixCls, scroll, data, getBodyWrapper, components } = this.context.table.props; const { expander, tableClassName, hasHead, hasBody, fixed, columns } = this.props; const tableStyle = {}; @@ -142,16 +144,20 @@ class BaseTable extends React.Component { } } + const Table = hasBody ? (components.table || 'table') : 'table'; + const BodyWrapper = components.body && components.body.wrapper || 'tbody'; + + return ( -
+
{hasHead && } {hasBody && getBodyWrapper( - + {this.renderRows(data, 0)} - + )} -
+
); } } diff --git a/src/Table.js b/src/Table.js index 36f5700a6..8aabfebc3 100644 --- a/src/Table.js +++ b/src/Table.js @@ -33,6 +33,19 @@ export default class Table extends React.Component { rowRef: PropTypes.func, getBodyWrapper: PropTypes.func, children: PropTypes.node, + components: PropTypes.shape({ + table: PropTypes.any, + header: PropTypes.shape({ + wrapper: PropTypes.any, + row: PropTypes.any, + cell: PropTypes.any, + }), + body: PropTypes.shape({ + wrapper: PropTypes.any, + row: PropTypes.any, + cell: PropTypes.any, + }), + }), ...ExpandableTable.PropTypes, } @@ -58,6 +71,7 @@ export default class Table extends React.Component { rowRef: () => null, getBodyWrapper: body => body, emptyText: () => 'No Data', + components: {}, } constructor(props) { diff --git a/src/TableCell.js b/src/TableCell.js index 797e1e5e5..2a8f3db9f 100644 --- a/src/TableCell.js +++ b/src/TableCell.js @@ -11,6 +11,7 @@ export default class TableCell extends React.Component { indentSize: PropTypes.number, column: PropTypes.object, expandIcon: PropTypes.node, + component: PropTypes.any, } isInvalidRenderCellText(text) { @@ -26,8 +27,16 @@ export default class TableCell extends React.Component { } render() { - const { record, indentSize, prefixCls, indent, - index, expandIcon, column } = this.props; + const { + record, + indentSize, + prefixCls, + indent, + index, + expandIcon, + column, + component: BodyCell, + } = this.props; const { dataIndex, render, className = '' } = column; // We should return undefined if no dataIndex is specified, but in order to @@ -70,7 +79,7 @@ export default class TableCell extends React.Component { return null; } return ( - + ); } } diff --git a/src/TableHeader.js b/src/TableHeader.js index 51f124fcf..45b794d99 100644 --- a/src/TableHeader.js +++ b/src/TableHeader.js @@ -34,7 +34,7 @@ function getHeaderRows(columns, currentRow = 0, rows) { } export default function TableHeader(props, { table }) { - const { prefixCls, showHeader } = table.props; + const { prefixCls, showHeader, components } = table.props; const { expander, columns, fixed } = props; if (!showHeader) { @@ -45,8 +45,10 @@ export default function TableHeader(props, { table }) { expander.renderExpandIndentCell(rows, fixed); + const HeaderWrapper = components.header && components.header.wrapper || 'thead'; + return ( - + { rows.map((row, index) => ( )) } - + ); } diff --git a/src/TableHeaderRow.js b/src/TableHeaderRow.js index 6b2301684..6df5c0512 100644 --- a/src/TableHeaderRow.js +++ b/src/TableHeaderRow.js @@ -1,13 +1,21 @@ import React from 'react'; import { connect } from 'mini-store'; -function TableHeaderRow({ row, height }) { +function TableHeaderRow({ row, height, components }) { const style = { height }; + let HeaderRow = 'tr'; + let HeaderCell = 'th'; + + if (components.header) { + HeaderRow = components.header.row || HeaderRow; + HeaderCell = components.header.cell || HeaderCell; + } + return ( - - {row.map((cell, i) => )} - + + {row.map((cell, i) => )} + ); } diff --git a/src/TableRow.js b/src/TableRow.js index bbb93bb42..90991f8a7 100644 --- a/src/TableRow.js +++ b/src/TableRow.js @@ -1,4 +1,5 @@ import React from 'react'; +import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import { connect } from 'mini-store'; import TableCell from './TableCell'; @@ -36,6 +37,7 @@ class TableRow extends React.Component { ]), renderExpandIcon: PropTypes.func, renderExpandIconCell: PropTypes.func, + components: PropTypes.any, } static defaultProps = { @@ -61,12 +63,24 @@ class TableRow extends React.Component { this.style = {}; } + componentDidMount() { + if (this.shouldRender) { + this.saveRowRef(); + } + } + componentWillReceiveProps(nextProps) { if (this.props.visible || (!this.props.visible && nextProps.visible)) { this.shouldRender = true; } } + componentDidUpdate() { + if (this.shouldRender && !this.rowRef) { + this.saveRowRef(); + } + } + onRowClick = (event) => { const { record, index, onRowClick } = this.props; onRowClick(record, index, event); @@ -102,6 +116,7 @@ class TableRow extends React.Component { store.setState({ expandedRowsHeight }); } +<<<<<<< HEAD getStyle() { const { height, visible } = this.props; @@ -116,12 +131,11 @@ class TableRow extends React.Component { return this.style; } - saveRowRef = (node) => { - this.rowRef = node; - if (node) { - if (!this.props.fixed) { - this.setHeight(); - } + saveRowRef() { + this.rowRef = ReactDOM.findDOMNode(this); + + if (!this.props.fixed) { + this.setHeight(); } } @@ -138,11 +152,20 @@ class TableRow extends React.Component { indent, indentSize, hovered, + components, hasExpandIcon, renderExpandIcon, renderExpandIconCell, } = this.props; + let BodyRow = 'tr'; + let BodyCell = 'td'; + + if (components && components.body) { + BodyRow = components.body.row || BodyRow; + BodyCell = components.body.cell || BodyCell; + } + let { className } = this.props; if (hovered) { @@ -164,6 +187,7 @@ class TableRow extends React.Component { column={columns[i]} key={columns[i].key || columns[i].dataIndex} expandIcon={hasExpandIcon(i) && renderExpandIcon()} + component={BodyCell} /> ); } @@ -172,8 +196,7 @@ class TableRow extends React.Component { `${prefixCls} ${className} ${prefixCls}-level-${indent}`.trim(); return ( - {cells} - + ); } } diff --git a/tests/Table.spec.js b/tests/Table.spec.js index 22aab3717..4895c41eb 100644 --- a/tests/Table.spec.js +++ b/tests/Table.spec.js @@ -440,4 +440,53 @@ describe('Table', () => { })); expect(wrapper).toMatchSnapshot(); }); + + describe('custom components', () => { + const MyTable = (props) => ; + const HeaderWrapper = (props) => ; + const HeaderRow = (props) => ; + const HeaderCell = (props) => ; + const BodyRow = (props) => ; + const BodyCell = (props) => + ) + ); +}; + +const rowSource = { + beginDrag(props) { + return { + index: props.index, + }; + }, +}; + +const rowTarget = { + drop(props, monitor) { + const dragIndex = monitor.getItem().index; + const hoverIndex = props.index; + + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return; + } + + // Time to actually perform the action + props.moveRow(dragIndex, hoverIndex); + + // Note: we're mutating the monitor item here! + // Generally it's better to avoid mutations, + // but it's good here for the sake of performance + // to avoid expensive index searches. + monitor.getItem().index = hoverIndex; + }, +}; + +BodyRow = DropTarget('row', rowTarget, (connect, monitor) => ({ + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + sourceClientOffset: monitor.getSourceClientOffset(), +}))( + DragSource('row', rowSource, (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + dragRow: monitor.getItem(), + clientOffset: monitor.getClientOffset(), + initialClientOffset: monitor.getInitialClientOffset(), + }))(BodyRow) +); + +const columns = [ + { title: 'title1', dataIndex: 'a', key: 'a', width: 100 }, + { id: '123', title: 'title2', dataIndex: 'b', key: 'b', width: 100 }, + { title: 'title3', dataIndex: 'c', key: 'c', width: 200 }, + { + title: 'Operations', + dataIndex: '', + key: 'd', + render() { + return Operations; + }, + }, +]; + +class Demo extends React.Component { + state = { + data: [ + { a: '123', key: '1' }, + { a: 'cdd', b: 'edd', key: '2' }, + { a: '1333', c: 'eee', d: 2, key: '3' }, + ], + } + + components = { + body: { + row: BodyRow, + }, + } + + moveRow = (dragIndex, hoverIndex) => { + const { data } = this.state; + const dragRow = data[dragIndex]; + + this.setState( + update(this.state, { + data: { + $splice: [[dragIndex, 1], [hoverIndex, 0, dragRow]], + }, + }), + ); + } + + render() { + return ( +
; + const BodyWrapper = (props) =>
; + + const components = { + table: MyTable, + header: { + wrapper: HeaderWrapper, + row: HeaderRow, + cell: HeaderCell, + }, + body: { + wrapper: BodyWrapper, + row: BodyRow, + cell: BodyCell, + }, + }; + + it('renders correctly', () => { + const wrapper = render(createTable({ components })); + + expect(wrapper).toMatchSnapshot(); + }); + + it('renders fixed column and header correctly', () => { + const columns = [ + { title: 'Name', dataIndex: 'name', key: 'name', fixed: 'left' }, + { title: 'Age', dataIndex: 'age', key: 'age' }, + { title: 'Gender', dataIndex: 'gender', key: 'gender', fixed: 'right' }, + ]; + const sampleData = [ + { key: 0, name: 'Lucy', age: 27, gender: 'F' }, + ]; + const wrapper = render(createTable({ + columns, + data: sampleData, + components, + scroll: { y: 100 }, + })); + + expect(wrapper).toMatchSnapshot(); + }); + }); }); diff --git a/tests/__snapshots__/Table.spec.js.snap b/tests/__snapshots__/Table.spec.js.snap index 98e9c233f..84864c745 100644 --- a/tests/__snapshots__/Table.spec.js.snap +++ b/tests/__snapshots__/Table.spec.js.snap @@ -1,5 +1,312 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Table custom components renders correctly 1`] = ` +
+
+
+ + + + + + + + + + + + + + + + + +
+ Name +
+ + Lucy +
+ + Jack +
+
+
+
+`; + +exports[`Table custom components renders fixed column and header correctly 1`] = ` +
+
+
+
+ + + + + + + + + + + + + +
+ Name + + Age + + Gender +
+
+
+ + + + + + + + + + + + + +
+ + Lucy + + 27 + + F +
+
+
+
+
+ + + + + + + + + +
+ Name +
+
+
+
+ + + + + + + + + +
+ + Lucy +
+
+
+
+
+
+ + + + + + + + + +
+ Gender +
+
+
+
+ + + + + + + + + +
+ F +
+
+
+
+
+
+`; + exports[`Table dataIndex render text by path 1`] = `
Date: Tue, 24 Oct 2017 21:09:34 +0800 Subject: [PATCH 2/3] Allow overriding elements --- .../{custom-component.html => react-dnd.html} | 0 examples/react-dnd.js | 187 ++++++++++++++++++ examples/styled-components.html | 0 ...stom-component.js => styled-components.js} | 23 +-- package.json | 8 +- src/BaseTable.js | 14 +- src/ExpandableTable.js | 7 + src/Table.js | 18 +- src/TableHeader.js | 5 +- src/TableHeaderRow.js | 10 +- src/TableRow.js | 14 +- tests/Table.spec.js | 10 + tests/__snapshots__/Table.spec.js.snap | 35 ++++ 13 files changed, 289 insertions(+), 42 deletions(-) rename examples/{custom-component.html => react-dnd.html} (100%) create mode 100644 examples/react-dnd.js create mode 100644 examples/styled-components.html rename examples/{custom-component.js => styled-components.js} (55%) diff --git a/examples/custom-component.html b/examples/react-dnd.html similarity index 100% rename from examples/custom-component.html rename to examples/react-dnd.html diff --git a/examples/react-dnd.js b/examples/react-dnd.js new file mode 100644 index 000000000..886b6f0b0 --- /dev/null +++ b/examples/react-dnd.js @@ -0,0 +1,187 @@ +/* eslint-disable no-unused-expressions,new-cap */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { injectGlobal } from 'styled-components'; +import update from 'immutability-helper'; +import { DragDropContext, DragSource, DropTarget } from 'react-dnd'; +import HTML5Backend from 'react-dnd-html5-backend'; +import Table from 'rc-table'; +import 'rc-table/assets/index.less'; + +injectGlobal` + tr.drop-over-downward td { + border-bottom: 2px dashed red; + } + + tr.drop-over-upward td { + border-top: 2px dashed red; + } +`; + +function dragDirection( + dragIndex, + hoverIndex, + initialClientOffset, + clientOffset, + sourceClientOffset, +) { + const hoverMiddleY = (initialClientOffset.y - sourceClientOffset.y) / 2; + const hoverClientY = clientOffset.y - sourceClientOffset.y; + if (dragIndex < hoverIndex && hoverClientY > hoverMiddleY) { + return 'downward'; + } + if (dragIndex > hoverIndex && hoverClientY < hoverMiddleY) { + return 'upward'; + } +} + +let BodyRow = (props) => { + const { + isOver, + connectDragSource, + connectDropTarget, + moveRow, + dragRow, + clientOffset, + sourceClientOffset, + initialClientOffset, + ...restProps, + } = props; + const style = { cursor: 'move' }; + + let className = restProps.className; + if (isOver && initialClientOffset) { + const direction = dragDirection( + dragRow.index, + restProps.index, + initialClientOffset, + clientOffset, + sourceClientOffset + ); + if (direction === 'downward') { + className += ' drop-over-downward'; + } + if (direction === 'upward') { + className += ' drop-over-upward'; + } + } + + return connectDragSource( + connectDropTarget( +
({ + index, + moveRow: this.moveRow, + })} + /> + ); + } +} + +Demo = DragDropContext(HTML5Backend)(Demo); + +ReactDOM.render( +
+

Integrate with react-dnd

+ +
, + document.getElementById('__react-content') +); diff --git a/examples/styled-components.html b/examples/styled-components.html new file mode 100644 index 000000000..e69de29bb diff --git a/examples/custom-component.js b/examples/styled-components.js similarity index 55% rename from examples/custom-component.js rename to examples/styled-components.js index 83ce2f741..fa453e009 100644 --- a/examples/custom-component.js +++ b/examples/styled-components.js @@ -1,5 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import styled from 'styled-components'; import Table from 'rc-table'; import 'rc-table/assets/index.less'; @@ -23,31 +24,21 @@ const data = [ { a: '1333', c: 'eee', d: 2, key: '3' }, ]; -const MyTable = props =>
; -const HeaderWrapper = props => ; -const HeaderRow = props => ; -const HeaderCell = props => ; -const BodyRow = props => ; -const BodyCell = props =>
; -const BodyWrapper = props =>
; +const BodyRow = styled.tr` + &:hover { + background: palevioletred !important; + } +`; const components = { - table: MyTable, - header: { - wrapper: HeaderWrapper, - row: HeaderRow, - cell: HeaderCell, - }, body: { - wrapper: BodyWrapper, row: BodyRow, - cell: BodyCell, }, }; ReactDOM.render(
-

Custom Component

+

Integrate with styled-components

, document.getElementById('__react-content') diff --git a/package.json b/package.json index e75884906..07a2df6b1 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "babel-runtime": "6.x", "component-classes": "^1.2.6", "lodash.get": "^4.4.2", + "lodash.merge": "^4.6.0", "mini-store": "^1.0.2", "prop-types": "^15.5.8", "rc-util": "^4.0.4", @@ -82,6 +83,7 @@ "enzyme": "^3.1.0", "enzyme-adapter-react-16": "^1.0.1", "enzyme-to-json": "^3.1.2", + "immutability-helper": "^2.4.0", "jest": "^21.2.1", "pre-commit": "1.x", "rc-animate": "^2.3.0", @@ -89,8 +91,12 @@ "rc-menu": "^5.0.11", "rc-tools": "7.x", "react": "^16.0.0", + "react-dnd": "^2.5.4", + "react-dnd-html5-backend": "^2.5.4", "react-dom": "^16.0.0", - "react-test-renderer": "^16.0.0" + "react-test-renderer": "^16.0.0", + "react-virtualized": "^9.12.0", + "styled-components": "^2.2.1" }, "pre-commit": [ "lint" diff --git a/src/BaseTable.js b/src/BaseTable.js index 98f4b2075..f4b7c59da 100644 --- a/src/BaseTable.js +++ b/src/BaseTable.js @@ -33,7 +33,7 @@ class BaseTable extends React.Component { renderRows = (renderData, indent, ancestorKeys = []) => { const { table } = this.context; - const { columnManager } = table; + const { columnManager, components } = table; const { prefixCls, childrenColumnName, @@ -44,7 +44,7 @@ class BaseTable extends React.Component { onRowContextMenu, onRowMouseEnter, onRowMouseLeave, - components, + onRow, } = table.props; const { getRowKey, fixed, expander } = this.props; @@ -96,6 +96,7 @@ class BaseTable extends React.Component { prefixCls={rowPrefixCls} childrenColumnName={childrenColumnName} columns={leafColumns} + onRow={onRow} onRowDoubleClick={onRowDoubleClick} onRowContextMenu={onRowContextMenu} onRowMouseEnter={onRowMouseEnter} @@ -131,7 +132,9 @@ class BaseTable extends React.Component { } render() { - const { prefixCls, scroll, data, getBodyWrapper, components } = this.context.table.props; + const { table } = this.context; + const { components } = table; + const { prefixCls, scroll, data, getBodyWrapper } = table.props; const { expander, tableClassName, hasHead, hasBody, fixed, columns } = this.props; const tableStyle = {}; @@ -144,9 +147,8 @@ class BaseTable extends React.Component { } } - const Table = hasBody ? (components.table || 'table') : 'table'; - const BodyWrapper = components.body && components.body.wrapper || 'tbody'; - + const Table = hasBody ? components.table : 'table'; + const BodyWrapper = components.body.wrapper; return (
diff --git a/src/ExpandableTable.js b/src/ExpandableTable.js index 797b49817..ff17a856c 100644 --- a/src/ExpandableTable.js +++ b/src/ExpandableTable.js @@ -146,6 +146,12 @@ class ExpandableTable extends React.Component { } const rowKey = `${ancestorKeys[0]}-extra-row`; + const components = { + body: { + row: 'tr', + cell: 'td', + }, + }; return ( ); } diff --git a/src/Table.js b/src/Table.js index 8aabfebc3..7a8e33fac 100644 --- a/src/Table.js +++ b/src/Table.js @@ -4,6 +4,7 @@ import { debounce, warningOnce } from './utils'; import shallowequal from 'shallowequal'; import addEventListener from 'rc-util/lib/Dom/addEventListener'; import { Provider, create } from 'mini-store'; +import merge from 'lodash.merge'; import ColumnManager from './ColumnManager'; import classes from 'component-classes'; import HeadTable from './HeadTable'; @@ -20,6 +21,7 @@ export default class Table extends React.Component { style: PropTypes.object, rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + onRow: PropTypes.func, onRowClick: PropTypes.func, onRowDoubleClick: PropTypes.func, onRowContextMenu: PropTypes.func, @@ -51,6 +53,7 @@ export default class Table extends React.Component { static childContextTypes = { table: PropTypes.any, + components: PropTypes.any, } static defaultProps = { @@ -58,6 +61,7 @@ export default class Table extends React.Component { useFixedHeader: false, rowKey: 'key', rowClassName: () => '', + onRow() {}, onRowClick() {}, onRowDoubleClick() {}, onRowContextMenu() {}, @@ -71,7 +75,6 @@ export default class Table extends React.Component { rowRef: () => null, getBodyWrapper: body => body, emptyText: () => 'No Data', - components: {}, } constructor(props) { @@ -96,6 +99,19 @@ export default class Table extends React.Component { props: this.props, columnManager: this.columnManager, saveRef: this.saveRef, + components: merge({ + table: 'table', + header: { + wrapper: 'thead', + row: 'tr', + cell: 'th', + }, + body: { + wrapper: 'tbody', + row: 'tr', + cell: 'td', + }, + }, this.props.components), }, }; } diff --git a/src/TableHeader.js b/src/TableHeader.js index 45b794d99..b356d807a 100644 --- a/src/TableHeader.js +++ b/src/TableHeader.js @@ -34,7 +34,8 @@ function getHeaderRows(columns, currentRow = 0, rows) { } export default function TableHeader(props, { table }) { - const { prefixCls, showHeader, components } = table.props; + const { components } = table; + const { prefixCls, showHeader } = table.props; const { expander, columns, fixed } = props; if (!showHeader) { @@ -45,7 +46,7 @@ export default function TableHeader(props, { table }) { expander.renderExpandIndentCell(rows, fixed); - const HeaderWrapper = components.header && components.header.wrapper || 'thead'; + const HeaderWrapper = components.header.wrapper; return ( diff --git a/src/TableHeaderRow.js b/src/TableHeaderRow.js index 6df5c0512..0507d3b70 100644 --- a/src/TableHeaderRow.js +++ b/src/TableHeaderRow.js @@ -3,14 +3,8 @@ import { connect } from 'mini-store'; function TableHeaderRow({ row, height, components }) { const style = { height }; - - let HeaderRow = 'tr'; - let HeaderCell = 'th'; - - if (components.header) { - HeaderRow = components.header.row || HeaderRow; - HeaderCell = components.header.cell || HeaderCell; - } + const HeaderRow = components.header.row; + const HeaderCell = components.header.cell; return ( diff --git a/src/TableRow.js b/src/TableRow.js index 90991f8a7..164b7f4d6 100644 --- a/src/TableRow.js +++ b/src/TableRow.js @@ -6,6 +6,7 @@ import TableCell from './TableCell'; class TableRow extends React.Component { static propTypes = { + onRow: PropTypes.func, onRowClick: PropTypes.func, onRowDoubleClick: PropTypes.func, onRowContextMenu: PropTypes.func, @@ -41,6 +42,7 @@ class TableRow extends React.Component { } static defaultProps = { + onRow() {}, onRowClick() {}, onRowDoubleClick() {}, onRowContextMenu() {}, @@ -116,7 +118,6 @@ class TableRow extends React.Component { store.setState({ expandedRowsHeight }); } -<<<<<<< HEAD getStyle() { const { height, visible } = this.props; @@ -149,6 +150,7 @@ class TableRow extends React.Component { columns, record, index, + onRow, indent, indentSize, hovered, @@ -158,13 +160,8 @@ class TableRow extends React.Component { renderExpandIconCell, } = this.props; - let BodyRow = 'tr'; - let BodyCell = 'td'; - - if (components && components.body) { - BodyRow = components.body.row || BodyRow; - BodyCell = components.body.cell || BodyCell; - } + const BodyRow = components.body.row; + const BodyCell = components.body.cell; let { className } = this.props; @@ -204,6 +201,7 @@ class TableRow extends React.Component { onContextMenu={this.onContextMenu} className={rowClassName} style={this.getStyle()} + {...onRow(record, index)} > {cells} diff --git a/tests/Table.spec.js b/tests/Table.spec.js index 4895c41eb..3c8ff5845 100644 --- a/tests/Table.spec.js +++ b/tests/Table.spec.js @@ -441,6 +441,16 @@ describe('Table', () => { expect(wrapper).toMatchSnapshot(); }); + it('onRow', () => { + const onRow = (record, index) => ({ + id: `row-${record.key}`, + index, + }); + const wrapper = render(createTable({ onRow })); + + expect(wrapper.find('tbody tr')).toMatchSnapshot(); + }); + describe('custom components', () => { const MyTable = (props) =>
; const HeaderWrapper = (props) => ; diff --git a/tests/__snapshots__/Table.spec.js.snap b/tests/__snapshots__/Table.spec.js.snap index 84864c745..5b98a6518 100644 --- a/tests/__snapshots__/Table.spec.js.snap +++ b/tests/__snapshots__/Table.spec.js.snap @@ -386,6 +386,41 @@ exports[`Table dataIndex render text by path 1`] = ` `; +exports[`Table onRow 1`] = ` +Array [ + + + , + + + , +] +`; + exports[`Table renders colSpan correctly 1`] = `
Date: Wed, 25 Oct 2017 15:41:58 +0800 Subject: [PATCH 3/3] Add onRow onHeaderRow column[onCell] and column[onHeaderCell] --- src/ExpandableTable.js | 8 +- src/Table.js | 2 + src/TableCell.js | 6 +- src/TableHeader.js | 6 +- src/TableHeaderRow.js | 14 ++- src/TableRow.js | 19 ++-- tests/Table.spec.js | 39 ++++++++- tests/__snapshots__/Table.spec.js.snap | 116 +++++++++++++++++-------- 8 files changed, 160 insertions(+), 50 deletions(-) diff --git a/src/ExpandableTable.js b/src/ExpandableTable.js index ff17a856c..9656def33 100644 --- a/src/ExpandableTable.js +++ b/src/ExpandableTable.js @@ -111,12 +111,14 @@ class ExpandableTable extends React.Component { return; } - rows[0].unshift({ - key: 'rc-table-expandIconAsCell', + const iconColumn = { + key: 'rc-table-expand-icon-cell', className: `${prefixCls}-expand-icon-th`, title: '', rowSpan: rows.length, - }); + }; + + rows[0].unshift({ ...iconColumn, column: iconColumn }); } renderExpandedRow(content, className, ancestorKeys, fixed) { diff --git a/src/Table.js b/src/Table.js index 7a8e33fac..0a511a827 100644 --- a/src/Table.js +++ b/src/Table.js @@ -22,6 +22,7 @@ export default class Table extends React.Component { rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), onRow: PropTypes.func, + onHeaderRow: PropTypes.func, onRowClick: PropTypes.func, onRowDoubleClick: PropTypes.func, onRowContextMenu: PropTypes.func, @@ -62,6 +63,7 @@ export default class Table extends React.Component { rowKey: 'key', rowClassName: () => '', onRow() {}, + onHeaderRow() {}, onRowClick() {}, onRowDoubleClick() {}, onRowContextMenu() {}, diff --git a/src/TableCell.js b/src/TableCell.js index 2a8f3db9f..2619da3da 100644 --- a/src/TableCell.js +++ b/src/TableCell.js @@ -63,6 +63,10 @@ export default class TableCell extends React.Component { } } + if (column.onCell) { + tdProps = { ...tdProps, ...column.onCell(record) }; + } + // Fix https://github.com/ant-design/ant-design/issues/1202 if (this.isInvalidRenderCellText(text)) { text = null; @@ -81,8 +85,8 @@ export default class TableCell extends React.Component { return ( {indentText} {expandIcon} diff --git a/src/TableHeader.js b/src/TableHeader.js index b356d807a..5cfcff8f2 100644 --- a/src/TableHeader.js +++ b/src/TableHeader.js @@ -16,6 +16,7 @@ function getHeaderRows(columns, currentRow = 0, rows) { key: column.key, className: column.className || '', children: column.title, + column, }; if (column.children) { getHeaderRows(column.children, currentRow + 1, rows); @@ -35,7 +36,7 @@ function getHeaderRows(columns, currentRow = 0, rows) { export default function TableHeader(props, { table }) { const { components } = table; - const { prefixCls, showHeader } = table.props; + const { prefixCls, showHeader, onHeaderRow } = table.props; const { expander, columns, fixed } = props; if (!showHeader) { @@ -54,11 +55,13 @@ export default function TableHeader(props, { table }) { rows.map((row, index) => ( )) } @@ -70,6 +73,7 @@ TableHeader.propTypes = { fixed: PropTypes.string, columns: PropTypes.array.isRequired, expander: PropTypes.object.isRequired, + onHeaderRow: PropTypes.func, }; TableHeader.contextTypes = { diff --git a/src/TableHeaderRow.js b/src/TableHeaderRow.js index 0507d3b70..bbf98c7cc 100644 --- a/src/TableHeaderRow.js +++ b/src/TableHeaderRow.js @@ -1,14 +1,20 @@ import React from 'react'; import { connect } from 'mini-store'; -function TableHeaderRow({ row, height, components }) { - const style = { height }; +function TableHeaderRow({ row, index, height, components, onHeaderRow }) { const HeaderRow = components.header.row; const HeaderCell = components.header.cell; + const rowProps = onHeaderRow(row.map(cell => cell.column), index); + const customStyle = rowProps ? rowProps.style : {}; + const style = { height, ...customStyle }; return ( - - {row.map((cell, i) => )} + + {row.map((cell, i) => { + const { column, ...cellProps } = cell; + const customProps = column.onHeaderCell ? column.onHeaderCell(column) : {}; + return ; + })} ); } diff --git a/src/TableRow.js b/src/TableRow.js index 164b7f4d6..a767a3f4f 100644 --- a/src/TableRow.js +++ b/src/TableRow.js @@ -60,9 +60,6 @@ class TableRow extends React.Component { super(props); this.shouldRender = props.visible; - - // avoid creating new object which may fail the sCU. - this.style = {}; } componentDidMount() { @@ -154,6 +151,8 @@ class TableRow extends React.Component { indent, indentSize, hovered, + height, + visible, components, hasExpandIcon, renderExpandIcon, @@ -192,6 +191,16 @@ class TableRow extends React.Component { const rowClassName = `${prefixCls} ${className} ${prefixCls}-level-${indent}`.trim(); + const rowProps = onRow(record, index); + const customStyle = rowProps ? rowProps.style : {}; + let style = { height }; + + if (!visible) { + style.display = 'none'; + } + + style = { ...style, ...customStyle }; + return ( {cells} diff --git a/tests/Table.spec.js b/tests/Table.spec.js index 3c8ff5845..16fde87f5 100644 --- a/tests/Table.spec.js +++ b/tests/Table.spec.js @@ -441,7 +441,7 @@ describe('Table', () => { expect(wrapper).toMatchSnapshot(); }); - it('onRow', () => { + it('renders onRow correctly', () => { const onRow = (record, index) => ({ id: `row-${record.key}`, index, @@ -451,6 +451,43 @@ describe('Table', () => { expect(wrapper.find('tbody tr')).toMatchSnapshot(); }); + it('renders column.onCell correctly', () => { + const onCell = (record) => ({ + id: `cell-${record.name}`, + }); + const columns = [ + { title: 'Name', dataIndex: 'name', key: 'name', onCell }, + ]; + const wrapper = render(createTable({ columns })); + + expect(wrapper.find('tbody td')).toMatchSnapshot(); + }); + + it('renders onHeaderRow correctly', () => { + const onHeaderRow = jest.fn((columns, index) => ({ + id: `header-row-${index}`, + })); + const wrapper = render(createTable({ onHeaderRow })); + + expect(wrapper.find('thead tr')).toMatchSnapshot(); + expect(onHeaderRow).toBeCalledWith( + [{ title: 'Name', dataIndex: 'name', key: 'name' }], + 0, + ); + }); + + it('renders column.onHeaderCell', () => { + const onHeaderCell = (column) => ({ + id: `header-cell-${column.key}`, + }); + const columns = [ + { title: 'Name', dataIndex: 'name', key: 'name', onHeaderCell }, + ]; + const wrapper = render(createTable({ columns })); + + expect(wrapper.find('thead th')).toMatchSnapshot(); + }); + describe('custom components', () => { const MyTable = (props) =>
+ + Lucy +
+ + Jack +
; const HeaderWrapper = (props) => ; diff --git a/tests/__snapshots__/Table.spec.js.snap b/tests/__snapshots__/Table.spec.js.snap index 5b98a6518..c9e320534 100644 --- a/tests/__snapshots__/Table.spec.js.snap +++ b/tests/__snapshots__/Table.spec.js.snap @@ -386,41 +386,6 @@ exports[`Table dataIndex render text by path 1`] = ` `; -exports[`Table onRow 1`] = ` -Array [ - - - , - - - , -] -`; - exports[`Table renders colSpan correctly 1`] = `
`; +exports[`Table renders column.onCell correctly 1`] = ` +Array [ +
, + , +] +`; + +exports[`Table renders column.onHeaderCell 1`] = ` + +`; + exports[`Table renders correctly 1`] = `
`; +exports[`Table renders onHeaderRow correctly 1`] = ` +
+ + +`; + +exports[`Table renders onRow correctly 1`] = ` +Array [ + + + , + + + , +] +`; + exports[`Table renders rowSpan correctly 1`] = `
- - Lucy -
- - Jack -
+ + Lucy + + + Jack + + Name +
+ Name +
+ + Lucy +
+ + Jack +