Browse Source

fix: controlled filterDropdown props in antd@1.x (#4470)

* controlled filterDropdown props in antd@1.x

* code style

* sync #4462
1.x-stable
Justin 8 years ago
committed by Benjy Cui
parent
commit
16ce494f92
  1. 86
      components/table/Table.jsx
  2. 123
      components/table/demo/custom-filter-panel.md
  3. 74
      components/table/filterDropdown.jsx
  4. 2
      components/table/index.md
  5. 6
      components/table/style/index.less
  6. 2
      components/upload/index.jsx

86
components/table/Table.jsx

@ -43,6 +43,7 @@ export default class Table extends React.Component {
bordered: React.PropTypes.bool,
onChange: React.PropTypes.func,
locale: React.PropTypes.object,
dropdownPrefixCls: React.PropTypes.string,
}
static defaultProps = {
@ -77,12 +78,12 @@ export default class Table extends React.Component {
selectionDirty: false,
...this.getSortStateFromColumns(),
pagination: this.hasPagination() ?
{
...defaultPagination,
...pagination,
current: pagination.defaultCurrent || pagination.current || 1,
pageSize: pagination.defaultPageSize || pagination.pageSize || 10,
} : {},
{
...defaultPagination,
...pagination,
current: pagination.defaultCurrent || pagination.current || 1,
pageSize: pagination.defaultPageSize || pagination.pageSize || 10,
} : {},
};
this.CheckboxPropsCache = {};
@ -498,10 +499,11 @@ export default class Table extends React.Component {
}
renderRowSelection() {
const { prefixCls, rowSelection } = this.props;
const columns = this.props.columns.concat();
if (this.props.rowSelection) {
if (rowSelection) {
const data = this.getFlatCurrentPageData().filter((item) => {
if (this.props.rowSelection.getCheckboxProps) {
if (rowSelection.getCheckboxProps) {
return !this.getCheckboxPropsByItem(item).disabled;
}
return true;
@ -519,27 +521,20 @@ export default class Table extends React.Component {
data.every(item => this.getCheckboxPropsByItem(item).defaultChecked)
);
}
let selectionColumn;
if (this.props.rowSelection.type === 'radio') {
selectionColumn = {
key: 'selection-column',
render: this.renderSelectionRadio,
className: 'ant-table-selection-column',
};
} else {
let selectionColumn = {
key: 'selection-column',
render: this.renderSelectionRadio,
className: `${prefixCls}-selection-column`,
};
if (rowSelection.type !== 'radio') {
const checkboxAllDisabled = data.every(item => this.getCheckboxPropsByItem(item).disabled);
const checkboxAll = (
selectionColumn.render = this.renderSelectionCheckBox;
selectionColumn.title = (
<Checkbox checked={checked}
disabled={checkboxAllDisabled}
onChange={this.handleSelectAllRow}
/>
);
selectionColumn = {
key: 'selection-column',
title: checkboxAll,
render: this.renderSelectionCheckBox,
className: 'ant-table-selection-column',
};
}
if (columns.some(column => column.fixed === 'left' || column.fixed === true)) {
selectionColumn.fixed = 'left';
@ -574,6 +569,7 @@ export default class Table extends React.Component {
}
renderColumnsDropdown(columns) {
const { prefixCls, dropdownPrefixCls } = this.props;
const { sortOrder } = this.state;
const locale = this.getLocale();
return columns.map((originColumn, i) => {
@ -589,6 +585,8 @@ export default class Table extends React.Component {
column={column}
selectedKeys={colFilters}
confirmFilter={this.handleFilter}
prefixCls={`${prefixCls}-filter`}
dropdownPrefixCls={dropdownPrefixCls || 'ant-dropdown'}
/>
);
}
@ -597,20 +595,20 @@ export default class Table extends React.Component {
if (isSortColumn) {
column.className = column.className || '';
if (sortOrder) {
column.className += ' ant-table-column-sort';
column.className += ` ${prefixCls}-column-sort`;
}
}
const isAscend = isSortColumn && sortOrder === 'ascend';
const isDescend = isSortColumn && sortOrder === 'descend';
sortButton = (
<div className="ant-table-column-sorter">
<span className={`ant-table-column-sorter-up ${isAscend ? 'on' : 'off'}`}
<div className={`${prefixCls}-column-sorter`}>
<span className={`${prefixCls}-column-sorter-up ${isAscend ? 'on' : 'off'}`}
title="↑"
onClick={() => this.toggleSortOrder('ascend', column)}
>
<Icon type="caret-up" />
</span>
<span className={`ant-table-column-sorter-down ${isDescend ? 'on' : 'off'}`}
<span className={`${prefixCls}-column-sorter-down ${isDescend ? 'on' : 'off'}`}
title="↓"
onClick={() => this.toggleSortOrder('descend', column)}
>
@ -756,16 +754,17 @@ export default class Table extends React.Component {
}
render() {
const { style, className, ...restProps } = this.props;
const { style, className, prefixCls, showHeader, ...restProps } = this.props;
const data = this.getCurrentPageData();
let columns = this.renderRowSelection();
const expandIconAsCell = this.props.expandedRowRender && this.props.expandIconAsCell !== false;
const locale = this.getLocale();
const classString = classNames({
[`ant-table-${this.props.size}`]: true,
'ant-table-bordered': this.props.bordered,
'ant-table-empty': !data.length,
[`${prefixCls}-${this.props.size}`]: true,
'${prefixCls}-bordered': this.props.bordered,
'${prefixCls}-empty': !data.length,
[`${prefixCls}-without-column-header`]: !showHeader,
});
columns = this.renderColumnsDropdown(columns);
@ -775,12 +774,20 @@ export default class Table extends React.Component {
return newColumn;
});
let expandIconColumnIndex = (columns[0] && columns[0].key === 'selection-column') ? 1 : 0;
if ('expandIconColumnIndex' in restProps) {
expandIconColumnIndex = restProps.expandIconColumnIndex;
}
let table = (
<RcTable {...restProps}
<RcTable
{...restProps}
prefixCls={prefixCls}
data={data}
columns={columns}
showHeader={showHeader}
className={classString}
expandIconColumnIndex={(columns[0] && columns[0].key === 'selection-column') ? 1 : 0}
expandIconColumnIndex={expandIconColumnIndex}
expandIconAsCell={expandIconAsCell}
emptyText={() => locale.emptyText}
/>
@ -788,14 +795,15 @@ export default class Table extends React.Component {
// if there is no pagination or no data,
// the height of spin should decrease by half of pagination
const paginationPatchClass = (this.hasPagination() && data && data.length !== 0)
? 'ant-table-with-pagination'
: 'ant-table-without-pagination';
const spinClassName = this.props.loading ? `${paginationPatchClass} ant-table-spin-holder` : '';
table = <Spin className={spinClassName} spinning={this.props.loading}>{table}</Spin>;
? `${prefixCls}-with-pagination`
: `${prefixCls}-without-pagination`;
const spinClassName = this.props.loading ? `${paginationPatchClass} ${prefixCls}-spin-holder` : '';
return (
<div className={`${className} clearfix`} style={style}>
{table}
{this.renderPagination()}
<Spin className={spinClassName} spinning={this.props.loading}>
{table}
{this.renderPagination()}
</Spin>
</div>
);
}

123
components/table/demo/custom-filter-panel.md

@ -0,0 +1,123 @@
---
order: 19
title: 自定义筛选菜单
---
## zh-CN
通过 `filterDropdown`、`filterDropdownVisible` 和 `filterDropdownVisibleChange` 定义自定义的列筛选功能,并实现一个搜索列的示例。
## en-US
Implement a customized column search example via `filterDropdown`, `filterDropdownVisible` and `filterDropdownVisibleChange`.
````jsx
import { Table, Input, Button } from 'antd';
const data = [{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
}, {
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
}, {
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
}, {
key: '4',
name: 'Jim Red',
age: 32,
address: 'London No. 2 Lake Park',
}];
const App = React.createClass({
getInitialState() {
return {
filterDropdownVisible: false,
data,
searchText: '',
};
},
onInputChange(e) {
this.setState({ searchText: e.target.value });
},
onSearch() {
const { searchText } = this.state;
const reg = new RegExp(searchText, 'gi');
this.setState({
filterDropdownVisible: false,
data: data.map((record) => {
const match = record.name.match(reg);
if (!match) {
return null;
}
return {
...record,
name: (
<span>
{record.name.split(reg).map((text, i) => (
i > 0 ? [<span className="highlight">{match[0]}</span>, text] : text
))}
</span>
),
};
}).filter(record => !!record),
});
},
render() {
const columns = [{
title: 'Name',
dataIndex: 'name',
key: 'name',
filterDropdown: (
<div className="custom-filter-dropdown">
<Input
placeholder="Search name"
value={this.state.searchText}
onChange={this.onInputChange}
onPressEnter={this.onSearch}
/>
<Button type="primary" onClick={this.onSearch}>Search</Button>
</div>
),
filterDropdownVisible: this.state.filterDropdownVisible,
onFilterDropdownVisibleChange: visible => this.setState({ filterDropdownVisible: visible }),
}, {
title: 'Age',
dataIndex: 'age',
key: 'age',
}, {
title: 'Address',
dataIndex: 'address',
key: 'address',
}];
return <Table columns={columns} dataSource={this.state.data} />;
},
});
ReactDOM.render(<App />, mountNode);
````
````css
.custom-filter-dropdown {
padding: 8px;
border-radius: 6px;
background: #fff;
box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
}
.custom-filter-dropdown input {
width: 130px;
margin-right: 8px;
}
.highlight {
color: #f50;
}
````

74
components/table/filterDropdown.jsx

@ -5,36 +5,56 @@ import Icon from '../icon';
import Checkbox from '../checkbox';
import Radio from '../radio';
const FilterDropdownMenuWrapper = ({ onClick, children }) => (
<div className="ant-table-filter-dropdown" onClick={onClick}>{children}</div>
const FilterDropdownMenuWrapper = ({ onClick, children, className }) => (
<div className={className} onClick={onClick}>{children}</div>
);
export default class FilterMenu extends React.Component {
static defaultProps = {
handleFilter() {},
column: null,
column: {},
}
constructor(props) {
super(props);
const visible = ('filterDropdownVisible' in props.column) ?
props.column.filterDropdownVisible : false;
this.state = {
selectedKeys: props.selectedKeys,
keyPathOfSelectedItem: {}, //
visible: false,
visible,
};
}
componentWillReceiveProps(nextProps) {
this.setState({
selectedKeys: nextProps.selectedKeys,
});
const { column } = nextProps;
let newState = {};
if ('selectedKeys' in nextProps) {
newState.selectedKeys = nextProps.selectedKeys;
}
if ('filterDropdownVisible' in column) {
newState.visible = column.filterDropdownVisible;
}
if (Object.keys(newState).length > 0) {
this.setState(newState);
}
}
setSelectedKeys = ({ selectedKeys }) => {
this.setState({ selectedKeys });
}
setVisible(visible) {
const { column } = this.props;
if (!('filterDropdownVisible' in column)) {
this.setState({ visible });
}
if (column.onFilterDropdownVisibleChange) {
column.onFilterDropdownVisibleChange(visible);
}
}
handleClearFilters = () => {
this.setState({
selectedKeys: [],
@ -42,16 +62,12 @@ export default class FilterMenu extends React.Component {
}
handleConfirm = () => {
this.setState({
visible: false,
});
this.setVisible(false);
this.confirmFilter();
}
onVisibleChange = (visible) => {
this.setState({
visible,
});
this.setVisible(visible);
if (!visible) {
this.confirmFilter();
}
@ -66,13 +82,14 @@ export default class FilterMenu extends React.Component {
renderMenuItem(item) {
const { column } = this.props;
const multiple = ('filterMultiple' in column) ? column.filterMultiple : true;
const input = multiple ? (
<Checkbox checked={this.state.selectedKeys.indexOf(item.value.toString()) >= 0} />
) : (
<Radio checked={this.state.selectedKeys.indexOf(item.value.toString()) >= 0} />
);
return (
<MenuItem key={item.value}>
{
multiple
? <Checkbox checked={this.state.selectedKeys.indexOf(item.value.toString()) >= 0} />
: <Radio checked={this.state.selectedKeys.indexOf(item.value.toString()) >= 0} />
}
{input}
<span>{item.text}</span>
</MenuItem>
);
@ -85,7 +102,7 @@ export default class FilterMenu extends React.Component {
const containSelected = Object.keys(keyPathOfSelectedItem).some(
key => keyPathOfSelectedItem[key].indexOf(item.value) >= 0
);
const subMenuCls = containSelected ? 'ant-dropdown-submenu-contain-selected' : '';
const subMenuCls = containSelected ? `${this.props.dropdownPrefixCls}-submenu-contain-selected` : '';
return (
<SubMenu title={item.text} className={subMenuCls} key={item.value.toString()}>
{item.children.map(child => this.renderMenuItem(child))}
@ -112,31 +129,34 @@ export default class FilterMenu extends React.Component {
}
render() {
const { column, locale } = this.props;
const { column, locale, prefixCls, dropdownPrefixCls } = this.props;
// default multiple selection in filter dropdown
const multiple = ('filterMultiple' in column) ? column.filterMultiple : true;
const menus = column.filterDropdown ? column.filterDropdown : (
const menus = column.filterDropdown ? (
<FilterDropdownMenuWrapper>
{column.filterDropdown}
</FilterDropdownMenuWrapper>
) : (
<FilterDropdownMenuWrapper className={`${prefixCls}-dropdown`}>
<Menu
multiple={multiple}
onClick={this.handleMenuItemClick}
prefixCls="ant-dropdown-menu"
prefixCls={`${dropdownPrefixCls}-menu`}
onSelect={this.setSelectedKeys}
onDeselect={this.setSelectedKeys}
selectedKeys={this.state.selectedKeys}
>
{this.renderMenus(column.filters)}
</Menu>
<div className="ant-table-filter-dropdown-btns">
<div className={`${prefixCls}-dropdown-btns`}>
<a
className="ant-table-filter-dropdown-link confirm"
className={`${prefixCls}-dropdown-link confirm`}
onClick={this.handleConfirm}
>
{locale.filterConfirm}
</a>
<a
className="ant-table-filter-dropdown-link clear"
className={`${prefixCls}-dropdown-link clear`}
onClick={this.handleClearFilters}
>
{locale.filterReset}
@ -146,7 +166,7 @@ export default class FilterMenu extends React.Component {
);
const dropdownSelectedClass = (this.props.selectedKeys.length > 0)
? 'ant-table-filter-selected' : '';
? `${prefixCls}-selected` : '';
return (
<Dropdown

2
components/table/index.md

@ -89,6 +89,8 @@ const columns = [{
| onFilter | 本地模式下,确定筛选的运行函数 | Function | - |
| filterMultiple | 是否多选 | Boolean | true |
| filterDropdown | 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互 | React.Element | - |
| filterDropdownVisible | 用于控制自定义筛选菜单是否可见 | Boolean | - |
| onFilterDropdownVisibleChange | 自定义筛选菜单可见变化时调用 | function(visible) {} | - |
| sorter | 排序函数,本地排序使用一个函数,需要服务端排序可设为 true | Function or Boolean | - |
| colSpan | 表头列合并,设置为 0 时,不渲染 | Number | |
| width | 列宽度 | String or Number | - |

6
components/table/style/index.less

@ -97,6 +97,12 @@
}
}
// https://github.com/ant-design/ant-design/issues/4373
&-without-column-header &-title + &-content,
&-without-column-header table {
border-radius: 0;
}
&-tbody > tr.@{table-prefix-cls}-row-selected {
background: #fafafa;
}

2
components/upload/index.jsx

@ -281,7 +281,7 @@ export default class Upload extends React.Component {
});
const uploadButton = (
<div className={uploadButtonCls} style={{ display: this.props.children ? '' : 'none'}}>
<div className={uploadButtonCls} style={{ display: this.props.children ? '' : 'none' }}>
<RcUpload {...props} ref="upload" />
</div>
);

Loading…
Cancel
Save