feat: Implement Table.Column and Table.ColumnGroup (#3868)

* Implement Table.Column and Table.ColumnGroup

* Upgrade rc-table
This commit is contained in:
Wei Zhu 2016-11-22 10:11:12 +08:00 committed by Benjy Cui
parent 1c26bca8fe
commit ed455c01d1
27 changed files with 338 additions and 47 deletions

View File

@ -0,0 +1,22 @@
import React from 'react';
import RcTable from 'rc-table';
export interface ColumnProps<T> {
title?: React.ReactNode;
key?: string;
dataIndex?: string;
render?: (text: any, record: T, index: number) => React.ReactNode;
filters?: { text: string; value: string }[];
onFilter?: (value: any, record: T) => boolean;
filterMultiple?: boolean;
filterDropdown?: React.ReactNode;
sorter?: boolean | ((a: any, b: any) => number);
colSpan?: number;
width?: string | number;
className?: string;
fixed?: boolean | ('left' | 'right');
filteredValue?: any[];
sortOrder?: boolean | ('ascend' | 'descend');
}
export default class Column<T> extends (RcTable.Column as React.ComponentClass<ColumnProps<T>>) {}

View File

@ -0,0 +1,8 @@
import React from 'react';
import RcTable from 'rc-table';
export interface ColumnGroupProps {
title?: React.ReactNode;
}
export default class ColumnGroup extends (RcTable.ColumnGroup as React.ComponentClass<ColumnGroupProps>) {}

View File

@ -5,13 +5,15 @@ import Pagination, { PaginationProps } from '../pagination';
import Icon from '../icon';
import Spin from '../spin';
import classNames from 'classnames';
import { flatArray, treeMap } from './util';
import { flatArray, treeMap, normalizeColumns } from './util';
import assign from 'object-assign';
import splitObject from '../_util/splitObject';
import warning from '../_util/warning';
import createStore, { Store } from './createStore';
import SelectionBox from './SelectionBox';
import SelectionCheckboxAll from './SelectionCheckboxAll';
import Column, { ColumnProps } from './Column';
import ColumnGroup from './ColumnGroup';
function noop() {
}
@ -44,24 +46,6 @@ export interface TableRowSelection<T> {
onSelectAll?: (selected: boolean, selectedRows: Object[], changeRows: Object[]) => any;
}
export interface TableColumnConfig<T> {
title?: React.ReactNode;
key?: string;
dataIndex?: string;
render?: (text: any, record: T, index: number) => React.ReactNode;
filters?: { text: string; value: string }[];
onFilter?: (value: any, record: T) => boolean;
filterMultiple?: boolean;
filterDropdown?: React.ReactNode;
sorter?: boolean | ((a: any, b: any) => number);
colSpan?: number;
width?: string | number;
className?: string;
fixed?: boolean | ('left' | 'right');
filteredValue?: any[];
sortOrder?: boolean | ('ascend' | 'descend');
}
export interface TableProps<T> {
prefixCls?: string;
dropdownPrefixCls?: string;
@ -69,7 +53,7 @@ export interface TableProps<T> {
pagination?: PaginationProps | boolean;
size?: 'default' | 'small';
dataSource?: T[];
columns: TableColumnConfig<T>[];
columns?: ColumnProps<T>[];
rowKey?: string | ((record: T, index: number) => string);
rowClassName?: (record: T, index: number) => string;
expandedRowRender?: any;
@ -100,9 +84,12 @@ export interface TableContext {
}
export default class Table<T> extends React.Component<TableProps<T>, any> {
static Column = Column;
static ColumnGroup = ColumnGroup;
static propTypes = {
dataSource: React.PropTypes.array,
columns: React.PropTypes.array.isRequired,
columns: React.PropTypes.array,
prefixCls: React.PropTypes.string,
useFixedHeader: React.PropTypes.bool,
rowSelection: React.PropTypes.object,
@ -136,6 +123,7 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
context: TableContext;
CheckboxPropsCache: Object;
store: Store;
columns: ColumnProps<T>[];
constructor(props) {
super(props);
@ -149,6 +137,8 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
const pagination = props.pagination || {};
this.columns = props.columns || normalizeColumns(props.children);
this.state = assign({}, this.getSortStateFromColumns(), {
// 减少状态
filters: this.getFiltersFromColumns(),
@ -225,6 +215,8 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
)) {
this.CheckboxPropsCache = {};
}
this.columns = nextProps.columns || normalizeColumns(nextProps.children);
}
if (this.getSortOrderColumns(nextProps.columns).length > 0) {
@ -292,11 +284,11 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
}
getSortOrderColumns(columns?) {
return (columns || this.props.columns || []).filter(column => 'sortOrder' in column);
return (columns || this.columns || []).filter(column => 'sortOrder' in column);
}
getFilteredValueColumns(columns?) {
return (columns || this.props.columns || []).filter(column => column.filteredValue);
return (columns || this.columns || []).filter(column => column.filteredValue);
}
getFiltersFromColumns(columns?) {
@ -376,7 +368,7 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
[this.getColumnKey(column)]: nextFilters,
});
// Remove filters not in current columns
const currentColumnKeys = props.columns.map(c => this.getColumnKey(c));
const currentColumnKeys = this.columns.map(c => this.getColumnKey(c));
Object.keys(filters).forEach((columnKey) => {
if (currentColumnKeys.indexOf(columnKey) < 0) {
delete filters[columnKey];
@ -571,7 +563,7 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
renderRowSelection() {
const { prefixCls, rowSelection } = this.props;
const columns = this.props.columns.concat();
const columns = this.columns.concat();
if (rowSelection) {
const data = this.getFlatCurrentPageData().filter((item) => {
if (rowSelection.getCheckboxProps) {
@ -579,7 +571,7 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
}
return true;
});
const selectionColumn: TableColumnConfig<any> = {
const selectionColumn: ColumnProps<any> = {
key: 'selection-column',
render: this.renderSelectionBox(rowSelection.type),
className: `${prefixCls}-selection-column`,
@ -743,7 +735,7 @@ export default class Table<T> extends React.Component<TableProps<T>, any> {
}
findColumn(myKey) {
return this.props.columns.filter(c => this.getColumnKey(c) === myKey)[0];
return this.columns.filter(c => this.getColumnKey(c) === myKey)[0];
}
getCurrentPageData() {

View File

@ -1,5 +1,5 @@
---
order: 10
order: 11
title:
en-US: border, title and footer
zh-CN: 带边框

View File

@ -1,5 +1,5 @@
---
order: 13
order: 14
title:
en-US: colSpan and rowSpan
zh-CN: 表格行/列合并

View File

@ -1,5 +1,5 @@
---
order: 21
order: 22
title:
en-US: Dynamic Settings
zh-CN: 动态控制表格属性

View File

@ -1,5 +1,5 @@
---
order: 15
order: 16
title:
en-US: Tree data
zh-CN: 树形数据展示

View File

@ -1,5 +1,5 @@
---
order: 12
order: 13
title:
en-US: Expandable Row
zh-CN: 可展开

View File

@ -1,5 +1,5 @@
---
order: 18
order: 19
title:
en-US: Fixed Columns and Header
zh-CN: 固定头和列

View File

@ -1,5 +1,5 @@
---
order: 17
order: 18
title:
en-US: Fixed Columns
zh-CN: 固定列

View File

@ -1,5 +1,5 @@
---
order: 16
order: 17
title:
en-US: Fixed Header
zh-CN: 固定表头

View File

@ -1,5 +1,5 @@
---
order: 20
order: 21
title:
en-US: Grouping table head
zh-CN: 表头分组

View File

@ -1,5 +1,5 @@
---
order: 6
order: 7
title:
en-US: Filter and sorter
zh-CN: 筛选和排序

View File

@ -0,0 +1,82 @@
---
order: 1
title:
en-US: JSX style API
zh-CN: JSX 风格的 API
---
## zh-CN
使用 JSX 风格的 API2.5.0 以后引入)
## en-US
Using JSX style API (introduced in 2.5.0)
````jsx
import { Table, Icon } from 'antd';
const { Column, ColumnGroup } = Table;
const data = [{
key: '1',
firstName: 'John',
lastName: 'Brown',
age: 32,
address: 'New York No. 1 Lake Park',
}, {
key: '2',
firstName: 'Jim',
lastName: 'Green',
age: 42,
address: 'London No. 1 Lake Park',
}, {
key: '3',
firstName: 'Joe',
lastName: 'Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
}];
ReactDOM.render(
<Table dataSource={data}>
<ColumnGroup title="Name">
<Column
title="First Name"
dataIndex="firstName"
key="firstName"
/>
<Column
title="Last Name"
dataIndex="lastName"
key="lastName"
/>
</ColumnGroup>
<Column
title="Age"
dataIndex="age"
key="age"
/>
<Column
title="Address"
dataIndex="address"
key="address"
/>
<Column
title="Action"
key="action"
render={(text, record) => (
<span>
<a href="#">Action 一 {record.name}</a>
<span className="ant-divider" />
<a href="#">Delete</a>
<span className="ant-divider" />
<a href="#" className="ant-dropdown-link">
More actions<Icon type="down" />
</a>
</span>
)}
/>
</Table>
, mountNode);
````

View File

@ -1,5 +1,5 @@
---
order: 8
order: 9
title:
en-US: No pagination
zh-CN: 不显示分页

View File

@ -1,5 +1,5 @@
---
order: 5
order: 6
title:
en-US: pagination
zh-CN: 分页

View File

@ -1,5 +1,5 @@
---
order: 6
order: 7
title:
en-US: Reset filters and sorters
zh-CN: 可控的筛选和排序

View File

@ -1,5 +1,5 @@
---
order: 2
order: 3
title:
en-US: Selection and operation
zh-CN: 选择和操作

View File

@ -1,5 +1,5 @@
---
order: 3
order: 4
title:
en-US: Checkbox props
zh-CN: 选择框属性

View File

@ -1,5 +1,5 @@
---
order: 1
order: 2
title:
en-US: selection
zh-CN: 可选择

View File

@ -1,5 +1,5 @@
---
order: 9
order: 10
title:
en-US: size
zh-CN: 紧凑型

View File

@ -78,7 +78,7 @@ const columns = [{
### Column
One of Property `columns` for describing column.
One of Property `columns` for descriping column, Column has the same API.
| Property | Description | Type | Default |
|---------------|--------------------------|-----------------|--------------|
@ -100,6 +100,12 @@ One of Property `columns` for describing column.
| fixed | set column to be fixed: `true`(same as left) `'left'` `'right'` | Boolean or String | false |
| sortOrder | controlled sorted value: `'ascend'` `'descend'` `false` | Boolean or String | - |
### ColumnGroup
| Property | Description | Type | Default |
|---------------|--------------------------|-----------------|--------------|
| title | title of the column group | String or React.Element | - |
### rowSelection
Properties for selection.

View File

@ -79,7 +79,7 @@ const columns = [{
### Column
列描述数据对象,是 columns 中的一项。
列描述数据对象,是 columns 中的一项Column 使用相同的 API
| 参数 | 说明 | 类型 | 默认值 |
|-----------|----------------------------|-----------------|---------|
@ -101,6 +101,12 @@ const columns = [{
| fixed | 列是否固定,可选 `true`(等效于 left) `'left'` `'right'` | Boolean or String | false |
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `'ascend'` `'descend'` `false` | Boolean or String | - |
### ColumnGroup
| 参数 | 说明 | 类型 | 默认值 |
|-----------|----------------------------|-----------------|---------|
| title | 列头显示文字 | String or React.Element | - |
### rowSelection
选择功能的配置。

View File

@ -1,4 +1,8 @@
import React from 'react';
import assign from 'object-assign';
import Column from './Column';
import ColumnGroup from './ColumnGroup';
export function flatArray(data: Object[] = [], childrenName = 'children') {
const result: Object[] = [];
const loop = (array) => {
@ -24,3 +28,25 @@ export function treeMap(tree: Object[], mapper: Function, childrenName = 'childr
return assign({}, mapper(node, index), extra);
});
}
export function normalizeColumns(elements) {
const columns: any[] = [];
React.Children.forEach(elements, (element: React.ReactElement<any>) => {
if (!isColumnElement(element)) {
return;
}
const column = assign({}, element.props);
if (element.key) {
column.key = element.key;
}
if (element.type as any === ColumnGroup) {
column.children = normalizeColumns(column.children);
}
columns.push(column);
});
return columns;
}
function isColumnElement(element) {
return element && (element.type === Column || element.type === ColumnGroup);
}

View File

@ -62,7 +62,7 @@
"rc-slider": "~5.3.0",
"rc-steps": "~2.2.0",
"rc-switch": "~1.4.2",
"rc-table": "~5.0.0",
"rc-table": "~5.2.0",
"rc-tabs": "~7.0.5",
"rc-time-picker": "~2.2.1",
"rc-tooltip": "~3.4.2",

View File

@ -2,6 +2,10 @@ import React from 'react';
import createStore from '../../components/table/createStore';
import Table from '../../components/table';
import TestUtils from 'react-addons-test-utils';
import { render } from 'enzyme';
import { renderToJson } from 'enzyme-to-json';
const { Column, ColumnGroup } = Table;
describe('Table', () => {
describe('row selection', () => {
@ -88,4 +92,44 @@ describe('Table', () => {
expect(checkboxes[2].disabled).toBe(true);
});
});
describe('JSX style API', () => {
it('renders correctly', () => {
const data = [{
key: '1',
firstName: 'John',
lastName: 'Brown',
age: 32,
}, {
key: '2',
firstName: 'Jim',
lastName: 'Green',
age: 42,
}];
const wrapper = render(
<Table dataSource={data} pagination={false}>
<ColumnGroup title="Name">
<Column
title="First Name"
dataIndex="firstName"
key="firstName"
/>
<Column
title="Last Name"
dataIndex="lastName"
key="lastName"
/>
</ColumnGroup>
<Column
title="Age"
dataIndex="age"
key="age"
/>
</Table>
);
expect(renderToJson(wrapper)).toMatchSnapshot();
});
});
})

View File

@ -0,0 +1,105 @@
exports[`Table JSX style API renders correctly 1`] = `
<div
class=" clearfix">
<div
class="ant-spin-nested-loading">
<div
class="ant-spin-container">
<div
class="ant-table ant-table-large ant-table-scroll-position-left">
<div
class="ant-table-content">
<div
class="">
<span>
<div
class="ant-table-body">
<table
class="">
<colgroup>
<col />
<col />
<col />
</colgroup>
<thead
class="ant-table-thead">
<tr>
<th
class=""
colspan="2">
<span>
Name
</span>
</th>
<th
class=""
rowspan="2">
<span>
Age
</span>
</th>
</tr>
<tr>
<th
class="">
<span>
First Name
</span>
</th>
<th
class="">
<span>
Last Name
</span>
</th>
</tr>
</thead>
<tbody
class="ant-table-tbody">
<tr
class="ant-table-row ant-table-row-level-0">
<td
class="">
<span
class="ant-table-row-indent indent-level-0"
style="padding-left:0px;" />
John
</td>
<td
class="">
Brown
</td>
<td
class="">
32
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-0">
<td
class="">
<span
class="ant-table-row-indent indent-level-0"
style="padding-left:0px;" />
Jim
</td>
<td
class="">
Green
</td>
<td
class="">
42
</td>
</tr>
</tbody>
</table>
</div>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
`;