1 | import React from 'react';
|
2 | import classNames from 'classnames';
|
3 | import List from './list';
|
4 | import Operation from './operation';
|
5 | import Search from './search';
|
6 | import PropTypes from 'prop-types';
|
7 | import { DragDropContext } from 'react-beautiful-dnd';
|
8 | import { reorder,move } from './utils';
|
9 |
|
10 | function noop() {}
|
11 |
|
12 | const defaultProps = {
|
13 | dataSource: [],
|
14 | render: noop,
|
15 | showSearch: false,
|
16 | searchPlaceholder: 'Search',
|
17 | notFoundContent: 'Not Found',
|
18 | showCheckbox: true,
|
19 | draggable: false,
|
20 | appendToBottom: false,
|
21 | renderOperation:()=>'',
|
22 | };
|
23 |
|
24 | const propTypes = {
|
25 | prefixCls: PropTypes.string,
|
26 | dataSource: PropTypes.array,
|
27 | render: PropTypes.func,
|
28 | targetKeys: PropTypes.array,
|
29 | onChange: PropTypes.func,
|
30 | height: PropTypes.number,
|
31 | listStyle: PropTypes.object,
|
32 | className: PropTypes.string,
|
33 | titles: PropTypes.array,
|
34 | operations: PropTypes.array,
|
35 | showSearch: PropTypes.bool,
|
36 | filterOption: PropTypes.func,
|
37 | searchPlaceholder: PropTypes.string,
|
38 | notFoundContent: PropTypes.node,
|
39 | body: PropTypes.func,
|
40 | footer: PropTypes.func,
|
41 | rowKey: PropTypes.func,
|
42 | lazy: PropTypes.object,
|
43 | showCheckbox: PropTypes.bool,
|
44 | draggable: PropTypes.bool,
|
45 | appendToBottom: PropTypes.bool,
|
46 | renderOperation:PropTypes.func
|
47 | };
|
48 |
|
49 | const defaultTitles = ['', ''];
|
50 | class Transfer extends React.Component{
|
51 |
|
52 | constructor(props) {
|
53 | super(props);
|
54 | const { selectedKeys = [], targetKeys = [] } = props;
|
55 | this.state = {
|
56 | leftFilter: '',
|
57 | rightFilter: '',
|
58 | sourceSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
|
59 | targetSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
|
60 | leftDataSource: [],
|
61 | rightDataSource: [],
|
62 | droppableId: '',
|
63 | draggingItemId: ''
|
64 | };
|
65 | this.cacheTargetKeys = [...targetKeys];
|
66 | }
|
67 | componentDidMount(){
|
68 | this.splitDataSource();
|
69 | }
|
70 |
|
71 | componentWillReceiveProps(nextProps) {
|
72 | const { sourceSelectedKeys, targetSelectedKeys } = this.state;
|
73 | if (nextProps.targetKeys !== this.props.targetKeys ||
|
74 | nextProps.dataSource !== this.props.dataSource ||
|
75 | nextProps.targetKeys !== this.cacheTargetKeys) {
|
76 |
|
77 | this.splitedDataSource = null;
|
78 |
|
79 | const { dataSource, targetKeys = [] } = nextProps;
|
80 | function existInDateSourcekey(key) {
|
81 | return dataSource.filter(item => item.key === key).length;
|
82 | }
|
83 |
|
84 |
|
85 | this.setState({
|
86 | sourceSelectedKeys: sourceSelectedKeys.filter(existInDateSourcekey)
|
87 | .filter(data => targetKeys.filter(key => key === data).length === 0),
|
88 | targetSelectedKeys: targetSelectedKeys.filter(existInDateSourcekey)
|
89 | .filter(data => targetKeys.filter(key => key === data).length > 0),
|
90 | });
|
91 |
|
92 | if(this.props.dataSource.length === 0 || !this.props.draggable){
|
93 | this.splitDataSource(targetKeys,dataSource);
|
94 | }
|
95 | }
|
96 | if (nextProps.selectedKeys) {
|
97 | const targetKeys = nextProps.targetKeys;
|
98 | this.setState({
|
99 | sourceSelectedKeys: nextProps.selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
|
100 | targetSelectedKeys: nextProps.selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
|
101 | });
|
102 | }
|
103 | }
|
104 |
|
105 | |
106 |
|
107 |
|
108 | addUniqueKey = (dataSource) => {
|
109 | const { rowKey } = this.props;
|
110 | if (rowKey) {
|
111 | dataSource.forEach(record => {
|
112 | record.key = rowKey(record);
|
113 | });
|
114 | }
|
115 | return dataSource;
|
116 | }
|
117 |
|
118 | |
119 |
|
120 |
|
121 |
|
122 |
|
123 | splitDataSource(newTargetKeys, newDataSource) {
|
124 |
|
125 | if (this.splitedDataSource) {
|
126 | return this.splitedDataSource;
|
127 | }
|
128 |
|
129 | let targetKeys = newTargetKeys || this.props.targetKeys;
|
130 |
|
131 | let dataSource = newDataSource || this.props.dataSource;
|
132 |
|
133 | dataSource = this.addUniqueKey(dataSource);
|
134 | this.allSourceKeys = dataSource.map(({key}) => key);
|
135 |
|
136 | const leftDataSource = dataSource.filter(({ key }) => targetKeys.indexOf(key) === -1);
|
137 |
|
138 |
|
139 | let rightDataSource = [];
|
140 | let tempIndex = -1;
|
141 | targetKeys.forEach((key) => {
|
142 | tempIndex = this.allSourceKeys.indexOf(key);
|
143 | rightDataSource.push(dataSource[tempIndex]);
|
144 | })
|
145 |
|
146 | this.splitedDataSource = {
|
147 | leftDataSource,
|
148 | rightDataSource,
|
149 | };
|
150 | this.setState({
|
151 | leftDataSource,
|
152 | rightDataSource,
|
153 | })
|
154 |
|
155 | return this.splitedDataSource;
|
156 | }
|
157 |
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 | splitDataSource2(newTargetKeys, newDataSource) {
|
164 |
|
165 | if (this.splitedDataSource) {
|
166 | return this.splitedDataSource;
|
167 | }
|
168 |
|
169 | let targetKeys = newTargetKeys || this.props.targetKeys;
|
170 |
|
171 | let sourceDataSource = this.props.dataSource;
|
172 | newDataSource = this.addUniqueKey(newDataSource);
|
173 | sourceDataSource = this.addUniqueKey(sourceDataSource);
|
174 | const leftDataSource = sourceDataSource.filter(({ key }) => targetKeys.indexOf(key) === -1);
|
175 | const rightDataSource = targetKeys.map(key => {
|
176 | return newDataSource.find(data => data.key === key)
|
177 | })
|
178 | this.splitedDataSource = {
|
179 | leftDataSource,
|
180 | rightDataSource,
|
181 | };
|
182 | this.setState({
|
183 | leftDataSource,
|
184 | rightDataSource,
|
185 | })
|
186 |
|
187 | return this.splitedDataSource;
|
188 | }
|
189 |
|
190 | moveTo = (direction, insertIndex) => {
|
191 | const { targetKeys = [], onChange, appendToBottom } = this.props;
|
192 | const { sourceSelectedKeys, targetSelectedKeys, leftDataSource, rightDataSource, droppableId } = this.state;
|
193 | const moveKeys = direction === 'right' ? sourceSelectedKeys : targetSelectedKeys;
|
194 |
|
195 | let temp = []
|
196 | if (appendToBottom) {
|
197 | temp = targetKeys.concat(moveKeys)
|
198 | } else if (insertIndex) {
|
199 | targetKeys.splice(insertIndex, 0, ...moveKeys)
|
200 | temp = targetKeys
|
201 | } else {
|
202 | temp = moveKeys.concat(targetKeys);
|
203 | }
|
204 |
|
205 | const newTargetKeys = direction === 'right'
|
206 | ? temp
|
207 | : targetKeys.filter(targetKey => moveKeys.indexOf(targetKey) === -1);
|
208 |
|
209 |
|
210 | const oppositeDirection = direction === 'right' ? 'left' : 'right';
|
211 | this.setState({
|
212 | [this.getSelectedKeysName(oppositeDirection)]: [],
|
213 | });
|
214 | this.handleSelectChange(oppositeDirection, []);
|
215 |
|
216 | if (onChange) {
|
217 | onChange(newTargetKeys, direction, moveKeys);
|
218 | }
|
219 |
|
220 | let newDataSource = leftDataSource.concat(rightDataSource);
|
221 | droppableId ? this.splitDataSource2(newTargetKeys,newDataSource) : this.splitDataSource(newTargetKeys);
|
222 | }
|
223 |
|
224 | moveToLeft = () => this.moveTo('left')
|
225 | moveToRight = insertIndex => this.moveTo('right', insertIndex)
|
226 |
|
227 | |
228 |
|
229 |
|
230 |
|
231 |
|
232 | handleSelectChange(direction, holder) {
|
233 |
|
234 | const { sourceSelectedKeys, targetSelectedKeys } = this.state;
|
235 | const onSelectChange = this.props.onSelectChange;
|
236 | if (!onSelectChange) {
|
237 | return;
|
238 | }
|
239 |
|
240 | if (direction === 'left') {
|
241 | onSelectChange(holder, targetSelectedKeys);
|
242 | } else {
|
243 | onSelectChange(sourceSelectedKeys, holder);
|
244 | }
|
245 | }
|
246 |
|
247 | handleSelectAll = (direction, filteredDataSource, checkAll) => {
|
248 | const holder = checkAll ? [] : filteredDataSource.map(item => item.key);
|
249 | this.handleSelectChange(direction, holder);
|
250 |
|
251 | if (!this.props.selectedKeys) {
|
252 | this.setState({
|
253 | [this.getSelectedKeysName(direction)]: holder,
|
254 | });
|
255 | }
|
256 | }
|
257 |
|
258 | |
259 |
|
260 |
|
261 |
|
262 |
|
263 | handleLeftSelectAll = (filteredDataSource, checkAll) => {
|
264 | this.handleSelectAll('left', filteredDataSource, checkAll)
|
265 | }
|
266 | handleRightSelectAll = (filteredDataSource, checkAll) => (
|
267 | this.handleSelectAll('right', filteredDataSource, checkAll)
|
268 | )
|
269 |
|
270 | |
271 |
|
272 |
|
273 |
|
274 |
|
275 | handleFilter = (direction, value) => {
|
276 | this.setState({
|
277 |
|
278 | [`${direction}Filter`]: value,
|
279 | });
|
280 | }
|
281 |
|
282 | handleLeftFilter = (v) => this.handleFilter('left', v)
|
283 | handleRightFilter = (v) => this.handleFilter('right', v)
|
284 |
|
285 | |
286 |
|
287 |
|
288 |
|
289 | handleClear = (direction) => {
|
290 | this.setState({
|
291 | [`${direction}Filter`]: '',
|
292 | });
|
293 | }
|
294 |
|
295 | handleLeftClear = () => this.handleClear('left')
|
296 | handleRightClear = () => this.handleClear('right')
|
297 |
|
298 | |
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 | handleSelect = (direction, selectedItem, checked) => {
|
305 | const { sourceSelectedKeys, targetSelectedKeys } = this.state;
|
306 | const holder = direction === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys];
|
307 | const index = holder.indexOf(selectedItem.key);
|
308 | if (checked) {
|
309 | holder.splice(index, 1);
|
310 | }else if(index === -1){
|
311 | holder.push(selectedItem.key);
|
312 | }
|
313 | this.handleSelectChange(direction, holder);
|
314 |
|
315 | if (!this.props.selectedKeys) {
|
316 | this.setState({
|
317 | [this.getSelectedKeysName(direction)]: holder,
|
318 | });
|
319 | }
|
320 | }
|
321 |
|
322 | handleLeftSelect = (selectedItem, checked) => this.handleSelect('left', selectedItem, checked);
|
323 | handleRightSelect = (selectedItem, checked) => this.handleSelect('right', selectedItem, checked);
|
324 |
|
325 | getTitles = () => {
|
326 | if (this.props.titles) {
|
327 | return this.props.titles;
|
328 | }
|
329 | if (this.context &&
|
330 | this.context.antLocale &&
|
331 | this.context.antLocale.Transfer
|
332 | ) {
|
333 | return this.context.antLocale.Transfer.titles || [];
|
334 | }
|
335 | return defaultTitles;
|
336 | }
|
337 |
|
338 | getSelectedKeysName(direction) {
|
339 | return direction === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys';
|
340 | }
|
341 |
|
342 | id2List = {
|
343 | droppable_1: 'leftDataSource',
|
344 | droppable_2: 'rightDataSource'
|
345 | };
|
346 |
|
347 | getList = id => this.state[this.id2List[id]];
|
348 |
|
349 | |
350 |
|
351 |
|
352 | onDragEnd = result => {
|
353 | this.setState({
|
354 | draggingItemId: ''
|
355 | })
|
356 | const { source, destination,draggableId } = result;
|
357 | let { targetKeys, onChange } = this.props;
|
358 | let sourceIndex = source ? source.index : '';
|
359 | let disIndex = destination ? destination.index : '';
|
360 | let temp;
|
361 |
|
362 |
|
363 | if (!destination) {
|
364 | return;
|
365 | }
|
366 |
|
367 | if (destination.droppableId === 'droppable_1') {
|
368 |
|
369 | if(source.droppableId === destination.droppableId) return;
|
370 |
|
371 | this.moveToLeft();
|
372 | return;
|
373 | }
|
374 |
|
375 |
|
376 | if (source.droppableId === destination.droppableId) {
|
377 | const items = reorder(
|
378 | this.getList(source.droppableId),
|
379 | targetKeys,
|
380 | sourceIndex,
|
381 | disIndex
|
382 | );
|
383 | this.setState({
|
384 | rightDataSource: items.dataArr,
|
385 | sourceSelectedKeys: [],
|
386 | targetSelectedKeys: []
|
387 | });
|
388 | if (onChange) {
|
389 | onChange(items.targetKeyArr, "", draggableId);
|
390 | }
|
391 | } else {
|
392 | if (this.state.sourceSelectedKeys.length > 1) {
|
393 | return this.moveToRight(destination.index)
|
394 | }
|
395 | const result = move(
|
396 | this.getList(source.droppableId),
|
397 | this.getList(destination.droppableId),
|
398 | source,
|
399 | destination,
|
400 | targetKeys
|
401 | )
|
402 | if (onChange) {
|
403 | onChange(result.newTargetKeys, "", draggableId);
|
404 | }
|
405 | this.setState({
|
406 | leftDataSource: result.droppable_1,
|
407 | rightDataSource: result.droppable_2,
|
408 | sourceSelectedKeys: [],
|
409 | targetSelectedKeys: []
|
410 | })
|
411 | }
|
412 | };
|
413 |
|
414 | |
415 |
|
416 |
|
417 | onDragStart = result => {
|
418 | let selectedItem = {};
|
419 | const { source } = result;
|
420 | selectedItem.key = result.draggableId;
|
421 | if(source.droppableId === 'droppable_1'){
|
422 | this.handleLeftSelect(selectedItem);
|
423 | }else if(source.droppableId === 'droppable_2'){
|
424 | this.handleRightSelect(selectedItem);
|
425 | }
|
426 | this.setState({
|
427 | droppableId : source.droppableId,
|
428 | draggingItemId: result.draggableId
|
429 | })
|
430 | }
|
431 |
|
432 | render() {
|
433 | const {
|
434 | prefixCls = 'u-transfer', operations = [], showSearch, notFoundContent,
|
435 | searchPlaceholder, body, footer, listStyle, className = '',
|
436 | filterOption, render, lazy, showCheckbox, draggable,renderOperation
|
437 | } = this.props;
|
438 | const { leftFilter, rightFilter, sourceSelectedKeys, targetSelectedKeys, leftDataSource, rightDataSource, droppableId, draggingItemId } = this.state;
|
439 |
|
440 |
|
441 | const leftActive = targetSelectedKeys.length > 0;
|
442 | const rightActive = sourceSelectedKeys.length > 0;
|
443 |
|
444 | const cls = classNames(className, prefixCls);
|
445 |
|
446 | const titles = this.getTitles();
|
447 | return (
|
448 | <div className={cls}>
|
449 | <DragDropContext onDragEnd={this.onDragEnd} onDragStart={this.onDragStart} >
|
450 | <List
|
451 | titleText={titles[0]}
|
452 | dataSource={leftDataSource}
|
453 | filter={leftFilter}
|
454 | filterOption={filterOption}
|
455 | style={listStyle}
|
456 | checkedKeys={sourceSelectedKeys}
|
457 | handleFilter={this.handleLeftFilter}
|
458 | handleClear={this.handleLeftClear}
|
459 | handleSelect={this.handleLeftSelect}
|
460 | handleSelectAll={this.handleLeftSelectAll}
|
461 | render={render}
|
462 | showSearch={showSearch}
|
463 | searchPlaceholder={searchPlaceholder}
|
464 | notFoundContent={notFoundContent}
|
465 | body={body}
|
466 | footer={footer}
|
467 | prefixCls={`${prefixCls}-list`}
|
468 | lazy={lazy}
|
469 | showCheckbox={showCheckbox}
|
470 | draggable={draggable}
|
471 | id={'1'}
|
472 | droppableId={droppableId}
|
473 | draggingItemId={draggingItemId}
|
474 | />
|
475 | {!draggable?
|
476 | <Operation
|
477 | rightActive={rightActive}
|
478 | rightArrowText={operations[0]}
|
479 | moveToRight={this.moveToRight}
|
480 | leftActive={leftActive}
|
481 | leftArrowText={operations[1]}
|
482 | moveToLeft={this.moveToLeft}
|
483 | className={`${prefixCls}-operation`}
|
484 | renderOperation={renderOperation}
|
485 | />
|
486 | : ''
|
487 | }
|
488 | <List
|
489 | titleText={titles[1]}
|
490 | dataSource={rightDataSource}
|
491 | filter={rightFilter}
|
492 | filterOption={filterOption}
|
493 | style={listStyle}
|
494 | checkedKeys={targetSelectedKeys}
|
495 | handleFilter={this.handleRightFilter}
|
496 | handleClear={this.handleRightClear}
|
497 | handleSelect={this.handleRightSelect}
|
498 | handleSelectAll={this.handleRightSelectAll}
|
499 | render={render}
|
500 | showSearch={showSearch}
|
501 | searchPlaceholder={searchPlaceholder}
|
502 | notFoundContent={notFoundContent}
|
503 | body={body}
|
504 | footer={footer}
|
505 | prefixCls={`${prefixCls}-list`}
|
506 | lazy={lazy}
|
507 | showCheckbox={showCheckbox}
|
508 | draggable={draggable}
|
509 | id={'2'}
|
510 | />
|
511 | </DragDropContext>
|
512 | </div>
|
513 | );
|
514 | }
|
515 | }
|
516 |
|
517 | Transfer.List = Transfer.List;
|
518 | Transfer.Operation = Transfer.Operation;
|
519 | Transfer.Search = Transfer.Search;
|
520 |
|
521 | Transfer.propTypes = propTypes;
|
522 | Transfer.defaultProps = defaultProps;
|
523 |
|
524 | export default Transfer;
|