diff --git a/assets/index.less b/assets/index.less
index a557042e3..5e1559929 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -1,15 +1,20 @@
@tablePrefixCls: rc-table;
@text-color : #666;
@font-size-base : 12px;
+@line-height: 1.5;
@table-border-color: #e9e9e9;
@table-head-background-color: #f3f3f3;
+@vertical-padding: 16px;
+@horizontal-padding: 8px;
+@row-height: @font-size-base * @line-height + @vertical-padding * 2;
+
.@{tablePrefixCls} {
font-size: @font-size-base;
color: @text-color;
transition: opacity 0.3s ease;
position: relative;
- line-height: 1.5;
+ line-height: @line-height;
overflow: hidden;
.@{tablePrefixCls}-scroll {
@@ -40,7 +45,7 @@
}
.@{tablePrefixCls}-title {
- padding: 16px 8px;
+ padding: @vertical-padding @horizontal-padding;
border-top: 1px solid #e9e9e9;
}
@@ -49,7 +54,7 @@
}
.@{tablePrefixCls}-footer {
- padding: 16px 8px;
+ padding: @vertical-padding @horizontal-padding;
border-bottom: 1px solid #e9e9e9;
}
@@ -81,7 +86,7 @@
}
th, td {
- padding: 16px 8px;
+ padding: @vertical-padding @horizontal-padding;
}
}
@@ -164,6 +169,15 @@
width: auto;
background: #fff;
}
+
+ .generate-rowspan(10);
+
+ .generate-rowspan(@n, @i: 1) when (@i =< @n) {
+ th.@{tablePrefixCls}-rowspan-@{i} {
+ height: @row-height * @i - @vertical-padding * 2;
+ }
+ .generate-rowspan(@n, (@i + 1));
+ }
}
&-fixed-left {
diff --git a/examples/grouping-columns.html b/examples/grouping-columns.html
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/grouping-columns.js b/examples/grouping-columns.js
new file mode 100644
index 000000000..095286a04
--- /dev/null
+++ b/examples/grouping-columns.js
@@ -0,0 +1,99 @@
+/* eslint-disable no-console,func-names,react/no-multi-comp */
+const React = require('react');
+const ReactDOM = require('react-dom');
+const Table = require('rc-table');
+require('rc-table/assets/index.less');
+
+const columns = [
+ {
+ title: '姓名',
+ dataIndex: 'name',
+ key: 'name',
+ },
+ {
+ title: '其它',
+ children: [
+ {
+ title: '年龄',
+ dataIndex: 'age',
+ key: 'age',
+ },
+ {
+ title: '住址',
+ children: [
+ {
+ title: '街道',
+ dataIndex: 'street',
+ key: 'street',
+ },
+ {
+ title: '小区',
+ children: [
+ {
+ title: '单元',
+ dataIndex: 'building',
+ key: 'building',
+ },
+ {
+ title: '门牌',
+ dataIndex: 'number',
+ key: 'number',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ title: '公司',
+ children: [
+ {
+ title: '地址',
+ dataIndex: 'companyAddress',
+ key: 'companyAddress',
+ },
+ {
+ title: '名称',
+ dataIndex: 'companyName',
+ key: 'companyName',
+ },
+ ],
+ },
+ {
+ title: '性别',
+ dataIndex: 'gender',
+ key: 'gender',
+ },
+];
+
+
+const data = [{
+ key: '1',
+ name: '胡彦斌',
+ age: 32,
+ street: '拱墅区和睦街道',
+ building: 1,
+ number: 2033,
+ companyAddress: '西湖区湖底公园',
+ companyName: '湖底有限公司',
+ gender: '男',
+}, {
+ key: '2',
+ name: '胡彦祖',
+ age: 42,
+ street: '拱墅区和睦街道',
+ building: 3,
+ number: 2035,
+ companyAddress: '西湖区湖底公园',
+ companyName: '湖底有限公司',
+ gender: '男',
+}];
+
+ReactDOM.render(
+
,
+ document.getElementById('__react-content')
+);
diff --git a/src/Table.jsx b/src/Table.jsx
index 76209b08a..9a582e349 100644
--- a/src/Table.jsx
+++ b/src/Table.jsx
@@ -189,30 +189,71 @@ const Table = React.createClass({
getHeader(columns, fixed) {
const { showHeader, expandIconAsCell, prefixCls } = this.props;
- let ths = [];
+ let rows;
+ if (columns) {
+ // columns are passed from fixed table function that already grouped.
+ rows = this.getHeaderRows(columns);
+ } else {
+ rows = this.getHeaderRows(this.groupColumns(this.getCurrentColumns()));
+ }
+
if (expandIconAsCell && fixed !== 'right') {
- ths.push({
+ rows[0].unshift({
key: 'rc-table-expandIconAsCell',
- className: `${prefixCls}-expand-icon-th`,
+ className: `${prefixCls}-expand-icon-th ${prefixCls}-rowspan-${rows.length}`,
title: '',
+ rowSpan: rows.length,
});
}
- ths = ths.concat(columns || this.getCurrentColumns()).map(c => {
- if (c.colSpan !== 0) {
- return {c.title} | ;
- }
- });
+
const { fixedColumnsHeadRowsHeight } = this.state;
const trStyle = (fixedColumnsHeadRowsHeight[0] && columns) ? {
height: fixedColumnsHeadRowsHeight[0],
} : null;
return showHeader ? (
- {ths}
+ {rows.map((r, i) => {
+ const ths = r.map(c => | );
+ return
;
+ })}
) : null;
},
+ getHeaderRows(columns, currentRow = 0, rows) {
+ const { prefixCls } = this.props;
+
+ rows = rows || [];
+ rows[currentRow] = rows[currentRow] || [];
+
+ columns.forEach(column => {
+ if (column.rowSpan && rows.length < column.rowSpan) {
+ while (rows.length < column.rowSpan) {
+ rows.push([]);
+ }
+ }
+ const cell = {
+ key: column.key,
+ className: column.className || '',
+ children: column.title,
+ };
+ if (column.children) {
+ this.getHeaderRows(column.children, currentRow + 1, rows);
+ }
+ if ('colSpan' in column) {
+ cell.colSpan = column.colSpan;
+ }
+ if ('rowSpan' in column) {
+ cell.rowSpan = column.rowSpan;
+ cell.className += ` ${prefixCls}-rowspan-${cell.rowSpan}`;
+ }
+ if (cell.colSpan !== 0) {
+ rows[currentRow].push(cell);
+ }
+ });
+ return rows;
+ },
+
getExpandedRow(key, content, visible, className, fixed) {
const prefixCls = this.props.prefixCls;
return (
@@ -224,7 +265,7 @@ const Table = React.createClass({
{(this.props.expandIconAsCell && fixed !== 'right')
? |
: null}
-
+ |
{fixed !== 'right' ? content : ' '}
|
@@ -271,6 +312,8 @@ const Table = React.createClass({
height: fixedColumnsBodyRowsHeight[i],
} : {};
+ const leafColumns = this.getLeafColumns(columns || this.getCurrentColumns());
+
rst.push(
);
}
- cols = cols.concat((columns || this.props.columns).map(c => {
+ const leafColumns = this.getLeafColumns(columns || this.props.columns);
+ cols = cols.concat(leafColumns.map(c => {
return ;
}));
return {cols};
@@ -361,7 +405,7 @@ const Table = React.createClass({
getLeftFixedTable() {
const { columns } = this.props;
- const fixedColumns = columns.filter(
+ const fixedColumns = this.groupColumns(columns).filter(
column => column.fixed === 'left' || column.fixed === true
);
return this.getTable({
@@ -372,7 +416,7 @@ const Table = React.createClass({
getRightFixedTable() {
const { columns } = this.props;
- const fixedColumns = columns.filter(column => column.fixed === 'right');
+ const fixedColumns = this.groupColumns(columns).filter(column => column.fixed === 'right');
return this.getTable({
columns: fixedColumns,
fixed: 'right',
@@ -517,6 +561,22 @@ const Table = React.createClass({
return Math.ceil((columnsPageRange[1] - columnsPageRange[0] + 1) / columnsPageSize) - 1;
},
+ getLeafColumns(columns) {
+ const leafColumns = [];
+ columns.forEach(column => {
+ if (!column.children) {
+ leafColumns.push(column);
+ } else {
+ leafColumns.push(...this.getLeafColumns(column.children));
+ }
+ });
+ return leafColumns;
+ },
+
+ getLeafColumnsCount(columns) {
+ return this.getLeafColumns(columns).length;
+ },
+
goToColumnsPage(currentColumnsPage) {
const maxColumnsPage = this.getMaxColumnsPage();
let page = currentColumnsPage;
@@ -531,6 +591,45 @@ const Table = React.createClass({
});
},
+ // add appropriate rowspan and colspan to column
+ groupColumns(columns, currentRow = 0, parentColumn = {}, rows = []) {
+ // track how many rows we got
+ if (!~rows.indexOf(currentRow)) {
+ rows.push(currentRow);
+ }
+ const grouped = [];
+ const setRowSpan = column => {
+ const rowSpan = rows.length - currentRow;
+ if (column &&
+ !column.children && // parent columns are supposed to be one row
+ rowSpan > 1 &&
+ (!column.rowSpan || column.rowSpan < rowSpan)
+ ) {
+ column.rowSpan = rowSpan;
+ }
+ };
+ columns.forEach((column, index) => {
+ const newColumn = { ...column };
+ parentColumn.colSpan = parentColumn.colSpan || 0;
+ if (newColumn.children && newColumn.children.length > 0) {
+ newColumn.children = this.groupColumns(newColumn.children, currentRow + 1, newColumn, rows);
+ parentColumn.colSpan = parentColumn.colSpan + newColumn.colSpan;
+ } else {
+ parentColumn.colSpan++;
+ }
+ // update rowspan to all previous columns
+ for (let i = 0; i < index; ++i) {
+ setRowSpan(grouped[i]);
+ }
+ // last column, update rowspan immediately
+ if (index + 1 === columns.length) {
+ setRowSpan(newColumn);
+ }
+ grouped.push(newColumn);
+ });
+ return grouped;
+ },
+
syncFixedTableRowHeight() {
const { prefixCls } = this.props;
const headRows = this.refs.headTable ? this.refs.headTable.querySelectorAll(`tr`) : [];
diff --git a/tests/GroupingColumns.spec.js b/tests/GroupingColumns.spec.js
new file mode 100644
index 000000000..6e01f2eaf
--- /dev/null
+++ b/tests/GroupingColumns.spec.js
@@ -0,0 +1,121 @@
+/* eslint-disable no-console,func-names,react/no-multi-comp */
+const expect = require('expect.js');
+const Table = require('../');
+const React = require('react');
+const ReactDOM = require('react-dom');
+const $ = require('jquery');
+
+describe('Table with grouping columns', () => {
+ let div;
+ let node;
+
+ beforeEach(() => {
+ div = document.createElement('div');
+ node = $(div);
+ });
+
+ it('group columns', () => {
+ /**
+ * +---+---+---------------+-------+---+
+ * | | | C | J | |
+ * | | +---+---------------+---+ |
+ * | | | | E | | | |
+ * | A | B | +---+-------+ | | M |
+ * | | | D | F | G | K | L | |
+ * | | | | +---+---+ | | |
+ * | | | | | H | I | | | |
+ * +---+---+---+---+---+---+---+---+---+
+ */
+ const columns = [
+ { title: '表头A', className: 'title-a', dataIndex: 'a', key: 'a' },
+ { title: '表头B', className: 'title-b', dataIndex: 'b', key: 'b' },
+ { title: '表头C', className: 'title-c', children:
+ [
+ { title: '表头D', className: 'title-d', dataIndex: 'c', key: 'c' },
+ { title: '表头E', className: 'title-e', children:
+ [
+ { title: '表头F', className: 'title-f', dataIndex: 'd', key: 'd' },
+ { title: '表头G', className: 'title-g', children:
+ [
+ { title: '表头H', className: 'title-h', dataIndex: 'e', key: 'e' },
+ { title: '表头I', className: 'title-i', dataIndex: 'f', key: 'f' },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ { title: '表头J', className: 'title-j', children:
+ [
+ { title: '表头K', className: 'title-k', dataIndex: 'g', key: 'g' },
+ { title: '表头L', className: 'title-l', dataIndex: 'h', key: 'h' },
+ ],
+ },
+ { title: '表头M', className: 'title-m', dataIndex: 'i', key: 'i' },
+ ];
+
+ const data = [
+ { key: '1', a: 'a1', b: 'b1', c: 'c1', d: 'd1', e: 'e1', f: 'f1', g: 'g1', h: 'h1', i: 'i1' },
+ { key: '2', a: 'a2', b: 'b2', c: 'c2', d: 'd2', e: 'e2', f: 'f2', g: 'g2', h: 'h2', i: 'i2' },
+ { key: '3', a: 'a3', b: 'b3', c: 'c3', d: 'd3', e: 'e3', f: 'f3', g: 'g3', h: 'h3', i: 'i3' },
+ ];
+
+ ReactDOM.render(
+ ,
+ div
+ );
+
+ const cells = {
+ 'title-a': ['4', undefined],
+ 'title-b': ['4', undefined],
+ 'title-c': [undefined, '4'],
+ 'title-d': ['3', undefined],
+ 'title-e': [undefined, '3'],
+ 'title-f': ['2', undefined],
+ 'title-g': [undefined, '2'],
+ 'title-h': [undefined, undefined],
+ 'title-i': [undefined, undefined],
+ 'title-j': [undefined, '2'],
+ 'title-k': ['3', undefined],
+ 'title-l': ['3', undefined],
+ 'title-m': ['4', undefined],
+ };
+ Object.keys(cells).forEach(className => {
+ const cell = cells[className];
+ expect(node.find(`.${className}`).attr('rowspan')).to.be(cell[0]);
+ expect(node.find(`.${className}`).attr('colspan')).to.be(cell[1]);
+ });
+ });
+
+ it('work with fixed columns', () => {
+ const columns = [
+ { title: '表头A', className: 'title-a', dataIndex: 'a', key: 'a', fixed: 'left' },
+ { title: '表头B', className: 'title-b', children:
+ [
+ { title: '表头C', className: 'title-c', dataIndex: 'b', key: 'b' },
+ { title: '表头D', className: 'title-d', dataIndex: 'c', key: 'c' },
+ ],
+ },
+ { title: '表头E', className: 'title-e', dataIndex: 'd', key: 'd', fixed: 'right' },
+ ];
+
+ const data = [
+ { key: '1', a: 'a1', b: 'b1', c: 'c1', d: 'd1' },
+ { key: '2', a: 'a2', b: 'b2', c: 'c2', d: 'd2' },
+ { key: '3', a: 'a3', b: 'b3', c: 'c3', d: 'd3' },
+ ];
+
+ ReactDOM.render(
+ ,
+ div
+ );
+
+ const titleA = node.find('.rc-table-fixed-left .title-a');
+ const titleE = node.find('.rc-table-fixed-right .title-e');
+
+ expect(titleA.attr('rowspan')).to.be('2');
+ expect(titleA.hasClass('rc-table-rowspan-2')).to.be(true);
+ expect(titleE.attr('rowspan')).to.be('2');
+ expect(titleE.hasClass('rc-table-rowspan-2')).to.be(true);
+ });
+});
diff --git a/tests/index.js b/tests/index.js
index de1faaea5..5f1d842d2 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -1,2 +1,3 @@
require('./index.spec.js');
require('./PagingColumns.spec.js');
+require('./GroupingColumns.spec.js');