2016-07-01 20:52:17 +08:00
|
|
|
import * as React from 'react';
|
2016-08-08 14:12:02 +08:00
|
|
|
import { PropTypes } from 'react';
|
2016-07-07 18:36:53 +08:00
|
|
|
import List from './list';
|
2015-12-25 17:53:35 +08:00
|
|
|
import Operation from './operation';
|
|
|
|
import Search from './search';
|
2015-12-24 17:44:54 +08:00
|
|
|
import classNames from 'classnames';
|
2016-07-01 20:52:17 +08:00
|
|
|
|
2015-11-25 23:17:06 +08:00
|
|
|
function noop() {
|
|
|
|
}
|
|
|
|
|
2016-07-01 20:52:17 +08:00
|
|
|
export interface TransferItem {
|
2016-07-13 11:14:24 +08:00
|
|
|
key: number | string;
|
|
|
|
title: string;
|
|
|
|
description?: string;
|
|
|
|
chosen: boolean;
|
2016-07-01 20:52:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Transfer
|
2016-07-07 20:25:03 +08:00
|
|
|
export interface TransferProps {
|
2016-07-13 11:14:24 +08:00
|
|
|
/** 数据源 */
|
|
|
|
dataSource: Array<TransferItem>;
|
|
|
|
/** 每行数据渲染函数 */
|
2016-07-13 17:22:23 +08:00
|
|
|
render?: (record: TransferItem) => any;
|
2016-07-13 11:14:24 +08:00
|
|
|
/** 显示在右侧框数据的key集合 */
|
|
|
|
targetKeys: Array<string>;
|
|
|
|
/** 变化时回调函数 */
|
2016-07-13 17:22:23 +08:00
|
|
|
onChange?: (targetKeys: Array<TransferItem>, direction: string, moveKeys: any) => void;
|
2016-07-13 11:14:24 +08:00
|
|
|
/** 两个穿梭框的自定义样式 */
|
|
|
|
listStyle?: React.CSSProperties;
|
2016-07-01 20:52:17 +08:00
|
|
|
/** 自定义类*/
|
2016-07-13 11:14:24 +08:00
|
|
|
className?: string;
|
|
|
|
/** 标题集合,顺序从左至右 */
|
|
|
|
titles?: Array<string>;
|
|
|
|
/** 操作文案集合,顺序从上至下 */
|
|
|
|
operations?: Array<string>;
|
|
|
|
/** 是否显示搜索框 */
|
|
|
|
showSearch?: boolean;
|
|
|
|
/** 搜索框的默认值 */
|
|
|
|
searchPlaceholder?: string;
|
|
|
|
/** 当列表为空时显示的内容 */
|
|
|
|
notFoundContent?: React.ReactNode | string;
|
|
|
|
/** 底部渲染函数 */
|
2016-07-13 17:22:23 +08:00
|
|
|
footer?: (props: any) => any;
|
2016-07-13 11:14:24 +08:00
|
|
|
style?: React.CSSProperties;
|
2016-07-01 20:52:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export default class Transfer extends React.Component<TransferProps, any> {
|
2016-03-29 14:01:10 +08:00
|
|
|
static List = List;
|
|
|
|
static Operation = Operation;
|
|
|
|
static Search = Search;
|
|
|
|
|
|
|
|
static defaultProps = {
|
|
|
|
prefixCls: 'ant-transfer',
|
|
|
|
dataSource: [],
|
|
|
|
render: noop,
|
|
|
|
targetKeys: [],
|
|
|
|
onChange: noop,
|
|
|
|
titles: ['源列表', '目的列表'],
|
|
|
|
operations: [],
|
|
|
|
showSearch: false,
|
|
|
|
body: noop,
|
|
|
|
footer: noop,
|
2016-05-12 11:09:27 +08:00
|
|
|
};
|
2016-03-29 14:01:10 +08:00
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
prefixCls: PropTypes.string,
|
|
|
|
dataSource: PropTypes.array,
|
|
|
|
render: PropTypes.func,
|
|
|
|
targetKeys: PropTypes.array,
|
|
|
|
onChange: PropTypes.func,
|
|
|
|
height: PropTypes.number,
|
|
|
|
listStyle: PropTypes.object,
|
|
|
|
className: PropTypes.string,
|
|
|
|
titles: PropTypes.array,
|
|
|
|
operations: PropTypes.array,
|
|
|
|
showSearch: PropTypes.bool,
|
2016-08-05 10:49:54 +08:00
|
|
|
filterOption: PropTypes.func,
|
2016-03-29 14:01:10 +08:00
|
|
|
searchPlaceholder: PropTypes.string,
|
|
|
|
notFoundContent: PropTypes.node,
|
|
|
|
body: PropTypes.func,
|
|
|
|
footer: PropTypes.func,
|
2016-05-30 16:59:21 +08:00
|
|
|
rowKey: PropTypes.func,
|
2016-05-12 11:09:27 +08:00
|
|
|
};
|
2015-11-25 23:17:06 +08:00
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
2015-11-26 16:07:11 +08:00
|
|
|
leftFilter: '',
|
|
|
|
rightFilter: '',
|
2015-12-21 15:29:02 +08:00
|
|
|
leftCheckedKeys: [],
|
2015-12-23 19:41:56 +08:00
|
|
|
rightCheckedKeys: [],
|
2015-11-25 23:17:06 +08:00
|
|
|
};
|
|
|
|
}
|
2016-07-15 14:01:51 +08:00
|
|
|
|
2016-05-06 12:09:26 +08:00
|
|
|
componentWillReceiveProps(nextProps) {
|
|
|
|
const { leftCheckedKeys, rightCheckedKeys } = this.state;
|
2016-06-21 17:41:39 +08:00
|
|
|
if (nextProps.targetKeys !== this.props.targetKeys ||
|
|
|
|
nextProps.dataSource !== this.props.dataSource) {
|
|
|
|
// clear cached splited dataSource
|
|
|
|
this.splitedDataSource = null;
|
|
|
|
|
|
|
|
const { dataSource, targetKeys } = nextProps;
|
|
|
|
// clear key nolonger existed
|
|
|
|
// clear checkedKeys according to targetKeys
|
|
|
|
this.setState({
|
|
|
|
leftCheckedKeys: leftCheckedKeys
|
|
|
|
.filter(data => dataSource.filter(item => item.key === data).length)
|
|
|
|
.filter(data => targetKeys.filter(key => key === data).length === 0),
|
|
|
|
rightCheckedKeys: rightCheckedKeys
|
|
|
|
.filter(data => dataSource.filter(item => item.key === data).length)
|
|
|
|
.filter(data => targetKeys.filter(key => key === data).length > 0),
|
|
|
|
});
|
|
|
|
}
|
2016-05-06 12:09:26 +08:00
|
|
|
}
|
|
|
|
splitDataSource(props) {
|
2016-06-21 17:41:39 +08:00
|
|
|
if (this.splitedDataSource) {
|
|
|
|
return this.splitedDataSource;
|
|
|
|
}
|
2016-05-30 16:59:21 +08:00
|
|
|
const { targetKeys } = props;
|
|
|
|
let { dataSource } = props;
|
2015-11-26 11:18:37 +08:00
|
|
|
|
2016-06-02 16:15:19 +08:00
|
|
|
if (props.rowKey) {
|
2016-05-30 16:59:21 +08:00
|
|
|
dataSource = dataSource.map(record => {
|
|
|
|
record.key = props.rowKey(record);
|
|
|
|
return record;
|
|
|
|
});
|
|
|
|
}
|
2016-02-23 16:28:41 +08:00
|
|
|
let leftDataSource = [...dataSource];
|
2015-12-21 15:29:02 +08:00
|
|
|
let rightDataSource = [];
|
2015-12-17 16:08:16 +08:00
|
|
|
|
2016-01-07 17:46:46 +08:00
|
|
|
if (targetKeys.length > 0) {
|
2015-12-21 15:29:02 +08:00
|
|
|
targetKeys.forEach((targetKey) => {
|
2016-01-27 16:46:42 +08:00
|
|
|
rightDataSource.push(leftDataSource.filter((data, index) => {
|
2016-01-07 17:46:46 +08:00
|
|
|
if (data.key === targetKey) {
|
2015-12-21 15:29:02 +08:00
|
|
|
leftDataSource.splice(index, 1);
|
|
|
|
return true;
|
|
|
|
}
|
2016-02-22 10:52:30 +08:00
|
|
|
return false;
|
2016-01-27 16:46:42 +08:00
|
|
|
})[0]);
|
2015-12-17 16:08:16 +08:00
|
|
|
});
|
|
|
|
}
|
2015-12-21 15:29:02 +08:00
|
|
|
|
2016-06-21 17:41:39 +08:00
|
|
|
this.splitedDataSource = {
|
2016-01-07 17:46:46 +08:00
|
|
|
leftDataSource,
|
|
|
|
rightDataSource,
|
2015-12-21 15:29:02 +08:00
|
|
|
};
|
2016-06-21 17:41:39 +08:00
|
|
|
|
|
|
|
return this.splitedDataSource;
|
2015-12-16 23:02:49 +08:00
|
|
|
}
|
|
|
|
|
2016-03-23 19:50:44 +08:00
|
|
|
moveTo = (direction) => {
|
2015-12-21 15:29:02 +08:00
|
|
|
const { targetKeys } = this.props;
|
|
|
|
const { leftCheckedKeys, rightCheckedKeys } = this.state;
|
2016-02-01 14:07:17 +08:00
|
|
|
const moveKeys = direction === 'right' ? leftCheckedKeys : rightCheckedKeys;
|
2015-12-21 15:29:02 +08:00
|
|
|
// move items to target box
|
2016-02-01 14:07:17 +08:00
|
|
|
const newTargetKeys = direction === 'right'
|
|
|
|
? moveKeys.concat(targetKeys)
|
|
|
|
: targetKeys.filter(targetKey => !moveKeys.some(checkedKey => targetKey === checkedKey));
|
2015-12-21 15:29:02 +08:00
|
|
|
|
|
|
|
// empty checked keys
|
|
|
|
this.setState({
|
2016-01-07 17:46:46 +08:00
|
|
|
[direction === 'right' ? 'leftCheckedKeys' : 'rightCheckedKeys']: [],
|
2015-12-21 15:29:02 +08:00
|
|
|
});
|
|
|
|
|
2016-02-01 14:07:17 +08:00
|
|
|
this.props.onChange(newTargetKeys, direction, moveKeys);
|
2015-11-25 23:17:06 +08:00
|
|
|
}
|
|
|
|
|
2016-03-30 17:06:19 +08:00
|
|
|
moveToLeft = () => this.moveTo('left')
|
|
|
|
moveToRight = () => this.moveTo('right')
|
|
|
|
|
2016-07-15 15:07:31 +08:00
|
|
|
handleSelectAll = (direction, filteredDataSource, checkAll) => {
|
|
|
|
const holder = checkAll ? [] : filteredDataSource.map(item => item.key);
|
2015-11-25 23:17:06 +08:00
|
|
|
|
|
|
|
this.setState({
|
2016-02-17 18:04:42 +08:00
|
|
|
[`${direction}CheckedKeys`]: holder,
|
2015-11-25 23:17:06 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-07-15 15:07:31 +08:00
|
|
|
handleLeftSelectAll = (...args) => this.handleSelectAll('left', ...args)
|
|
|
|
handleRightSelectAll = (...args) => this.handleSelectAll('right', ...args)
|
2016-03-30 17:06:19 +08:00
|
|
|
|
2016-03-23 19:50:44 +08:00
|
|
|
handleFilter = (direction, e) => {
|
2015-12-21 15:29:02 +08:00
|
|
|
this.setState({
|
2015-12-23 19:41:56 +08:00
|
|
|
// add filter
|
2016-02-17 18:04:42 +08:00
|
|
|
[`${direction}Filter`]: e.target.value,
|
2015-12-21 15:29:02 +08:00
|
|
|
});
|
2015-11-26 16:07:11 +08:00
|
|
|
}
|
|
|
|
|
2016-03-30 17:06:19 +08:00
|
|
|
handleLeftFilter = (e) => this.handleFilter('left', e)
|
|
|
|
handleRightFilter = (e) => this.handleFilter('right', e)
|
|
|
|
|
2016-03-23 19:50:44 +08:00
|
|
|
handleClear = (direction) => {
|
2015-12-21 15:29:02 +08:00
|
|
|
this.setState({
|
2016-02-17 18:04:42 +08:00
|
|
|
[`${direction}Filter`]: '',
|
2015-11-25 23:17:06 +08:00
|
|
|
});
|
2015-12-21 15:29:02 +08:00
|
|
|
}
|
2015-11-25 23:17:06 +08:00
|
|
|
|
2016-03-30 17:06:19 +08:00
|
|
|
handleLeftClear = () => this.handleClear('left')
|
|
|
|
handleRightClear = () => this.handleClear('right')
|
|
|
|
|
2016-03-23 19:50:44 +08:00
|
|
|
handleSelect = (direction, selectedItem, checked) => {
|
2015-12-21 15:29:02 +08:00
|
|
|
const { leftCheckedKeys, rightCheckedKeys } = this.state;
|
2016-06-21 17:41:39 +08:00
|
|
|
const holder = direction === 'left' ? [...leftCheckedKeys] : [...rightCheckedKeys];
|
2016-01-27 16:46:42 +08:00
|
|
|
let index;
|
|
|
|
holder.forEach((key, i) => {
|
|
|
|
if (key === selectedItem.key) {
|
|
|
|
index = i;
|
|
|
|
}
|
|
|
|
});
|
2016-01-07 17:46:46 +08:00
|
|
|
if (index > -1) {
|
2015-12-21 15:29:02 +08:00
|
|
|
holder.splice(index, 1);
|
|
|
|
}
|
2016-01-07 17:46:46 +08:00
|
|
|
if (checked) {
|
2015-12-21 15:29:02 +08:00
|
|
|
holder.push(selectedItem.key);
|
|
|
|
}
|
2015-11-25 23:17:06 +08:00
|
|
|
this.setState({
|
2016-02-17 18:04:42 +08:00
|
|
|
[`${direction}CheckedKeys`]: holder,
|
2015-11-25 23:17:06 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-03-30 17:06:19 +08:00
|
|
|
handleLeftSelect = (selectedItem, checked) => this.handleSelect('left', selectedItem, checked);
|
|
|
|
handleRightSelect = (selectedItem, checked) => this.handleSelect('right', selectedItem, checked);
|
|
|
|
|
2015-11-25 23:17:06 +08:00
|
|
|
render() {
|
2015-12-24 17:44:54 +08:00
|
|
|
const {
|
2016-03-07 12:01:49 +08:00
|
|
|
prefixCls, titles, operations, showSearch, notFoundContent,
|
2015-12-24 17:44:54 +08:00
|
|
|
searchPlaceholder, body, footer, listStyle, className,
|
2016-08-05 10:49:54 +08:00
|
|
|
filterOption, render,
|
2015-12-24 17:44:54 +08:00
|
|
|
} = this.props;
|
2015-12-21 15:29:02 +08:00
|
|
|
const { leftFilter, rightFilter, leftCheckedKeys, rightCheckedKeys } = this.state;
|
2015-11-25 23:17:06 +08:00
|
|
|
|
2016-05-06 12:09:26 +08:00
|
|
|
const { leftDataSource, rightDataSource } = this.splitDataSource(this.props);
|
2015-12-23 19:41:56 +08:00
|
|
|
const leftActive = rightCheckedKeys.length > 0;
|
|
|
|
const rightActive = leftCheckedKeys.length > 0;
|
|
|
|
|
2015-12-24 17:44:54 +08:00
|
|
|
const cls = classNames({
|
|
|
|
[className]: !!className,
|
2016-03-29 17:19:31 +08:00
|
|
|
[prefixCls]: true,
|
2015-12-24 17:44:54 +08:00
|
|
|
});
|
|
|
|
|
2016-01-07 14:21:29 +08:00
|
|
|
return (
|
|
|
|
<div className={cls}>
|
|
|
|
<List titleText={titles[0]}
|
2016-01-07 17:46:46 +08:00
|
|
|
dataSource={leftDataSource}
|
|
|
|
filter={leftFilter}
|
2016-08-05 10:49:54 +08:00
|
|
|
filterOption={filterOption}
|
2016-01-07 17:46:46 +08:00
|
|
|
style={listStyle}
|
|
|
|
checkedKeys={leftCheckedKeys}
|
2016-03-30 17:06:19 +08:00
|
|
|
handleFilter={this.handleLeftFilter}
|
|
|
|
handleClear={this.handleLeftClear}
|
|
|
|
handleSelect={this.handleLeftSelect}
|
|
|
|
handleSelectAll={this.handleLeftSelectAll}
|
2016-05-12 11:09:27 +08:00
|
|
|
render={render}
|
2016-01-07 17:46:46 +08:00
|
|
|
showSearch={showSearch}
|
|
|
|
searchPlaceholder={searchPlaceholder}
|
2016-03-07 12:01:49 +08:00
|
|
|
notFoundContent={notFoundContent}
|
2016-01-07 17:46:46 +08:00
|
|
|
body={body}
|
|
|
|
footer={footer}
|
2016-06-06 13:54:10 +08:00
|
|
|
prefixCls={`${prefixCls}-list`}
|
|
|
|
/>
|
2016-01-07 14:21:29 +08:00
|
|
|
<Operation rightActive={rightActive}
|
2016-01-07 17:46:46 +08:00
|
|
|
rightArrowText={operations[0]}
|
2016-03-30 17:06:19 +08:00
|
|
|
moveToRight={this.moveToRight}
|
2016-01-07 17:46:46 +08:00
|
|
|
leftActive={leftActive}
|
|
|
|
leftArrowText={operations[1]}
|
2016-03-30 17:06:19 +08:00
|
|
|
moveToLeft={this.moveToLeft}
|
2016-06-06 13:54:10 +08:00
|
|
|
className={`${prefixCls}-operation`}
|
|
|
|
/>
|
2016-01-07 14:21:29 +08:00
|
|
|
<List titleText={titles[1]}
|
2016-01-07 17:46:46 +08:00
|
|
|
dataSource={rightDataSource}
|
|
|
|
filter={rightFilter}
|
2016-08-05 10:49:54 +08:00
|
|
|
filterOption={filterOption}
|
2016-01-07 17:46:46 +08:00
|
|
|
style={listStyle}
|
|
|
|
checkedKeys={rightCheckedKeys}
|
2016-03-30 17:06:19 +08:00
|
|
|
handleFilter={this.handleRightFilter}
|
|
|
|
handleClear={this.handleRightClear}
|
|
|
|
handleSelect={this.handleRightSelect}
|
|
|
|
handleSelectAll={this.handleRightSelectAll}
|
2016-05-12 11:09:27 +08:00
|
|
|
render={render}
|
2016-01-07 17:46:46 +08:00
|
|
|
showSearch={showSearch}
|
|
|
|
searchPlaceholder={searchPlaceholder}
|
2016-03-07 12:01:49 +08:00
|
|
|
notFoundContent={notFoundContent}
|
2016-01-07 17:46:46 +08:00
|
|
|
body={body}
|
|
|
|
footer={footer}
|
2016-06-06 13:54:10 +08:00
|
|
|
prefixCls={`${prefixCls}-list`}
|
|
|
|
/>
|
2016-01-07 14:21:29 +08:00
|
|
|
</div>
|
|
|
|
);
|
2015-11-25 23:17:06 +08:00
|
|
|
}
|
|
|
|
}
|