You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
403 lines
12 KiB
403 lines
12 KiB
7 years ago
|
import PropTypes from '../_util/vue-types'
|
||
|
import { initDefaultProps, getOptionProps, getComponentFromProp } from '../_util/props-util'
|
||
|
import BaseMixin from '../_util/BaseMixin'
|
||
|
import classNames from 'classnames'
|
||
|
import List from './list'
|
||
|
import Operation from './operation'
|
||
|
// import Search from './search'
|
||
|
import LocaleReceiver from '../locale-provider/LocaleReceiver'
|
||
|
import defaultLocale from '../locale-provider/default'
|
||
|
|
||
|
// function noop () {
|
||
|
// }
|
||
|
|
||
|
export const TransferDirection = 'left' | 'right'
|
||
|
|
||
|
export const TransferItem = PropTypes.shape({
|
||
|
key: PropTypes.string,
|
||
|
title: PropTypes.string,
|
||
|
description: PropTypes.string,
|
||
|
disabled: PropTypes.bool,
|
||
|
}).loose
|
||
|
|
||
|
export const TransferProps = {
|
||
|
prefixCls: PropTypes.string,
|
||
|
dataSource: PropTypes.arrayOf(TransferItem),
|
||
|
targetKeys: PropTypes.arrayOf(PropTypes.string),
|
||
|
selectedKeys: PropTypes.arrayOf(PropTypes.string),
|
||
|
render: PropTypes.func,
|
||
|
listStyle: PropTypes.object,
|
||
|
titles: PropTypes.arrayOf(TransferItem),
|
||
|
operations: PropTypes.arrayOf(TransferItem),
|
||
|
showSearch: PropTypes.bool,
|
||
|
filterOption: PropTypes.func,
|
||
|
searchPlaceholder: PropTypes.string,
|
||
|
notFoundContent: PropTypes.any,
|
||
|
rowKey: PropTypes.string,
|
||
|
lazy: PropTypes.oneOfType([
|
||
|
PropTypes.object,
|
||
|
PropTypes.bool,
|
||
|
]),
|
||
|
}
|
||
|
|
||
|
export const TransferLocale = {
|
||
|
titles: PropTypes.arrayOf(PropTypes.string),
|
||
|
notFoundContent: PropTypes.string,
|
||
|
searchPlaceholder: PropTypes.string,
|
||
|
itemUnit: PropTypes.string,
|
||
|
itemsUnit: PropTypes.string,
|
||
|
}
|
||
|
|
||
|
const tranferProps = {
|
||
|
prefixCls: PropTypes.string,
|
||
|
dataSource: PropTypes.arrayOf(TransferItem),
|
||
|
targetKeys: PropTypes.array,
|
||
|
listStyle: PropTypes.object,
|
||
|
render: PropTypes.func,
|
||
|
titles: PropTypes.array,
|
||
|
operations: PropTypes.array,
|
||
|
showSearch: PropTypes.bool,
|
||
|
filterOption: PropTypes.func,
|
||
|
searchPlaceholder: PropTypes.string,
|
||
|
notFoundContent: PropTypes.string,
|
||
|
rowKey: PropTypes.func,
|
||
|
lazy: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
|
||
|
}
|
||
|
|
||
|
export default {
|
||
|
name: 'Transfer',
|
||
|
mixins: [BaseMixin],
|
||
|
props: initDefaultProps(tranferProps, {
|
||
|
dataSource: [],
|
||
|
showSearch: false,
|
||
|
}),
|
||
|
data () {
|
||
|
this.splitedDataSource = {
|
||
|
leftDataSource: [],
|
||
|
rightDataSource: [],
|
||
|
} | null
|
||
|
const { selectedKeys = [], targetKeys = [] } = this
|
||
|
return {
|
||
|
leftFilter: '',
|
||
|
rightFilter: '',
|
||
|
sourceSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
|
||
|
targetSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
|
||
|
}
|
||
|
},
|
||
|
mounted () {
|
||
|
this.currentProps = { ...this.$props }
|
||
|
},
|
||
|
watch: {
|
||
|
'$props': {
|
||
|
handler: function (nextProps) {
|
||
|
const { sourceSelectedKeys, targetSelectedKeys, currentProps } = this
|
||
|
|
||
|
if (nextProps.targetKeys !== currentProps.targetKeys ||
|
||
|
nextProps.dataSource !== currentProps.dataSource) {
|
||
|
// clear cached splited dataSource
|
||
|
this.splitedDataSource = null
|
||
|
|
||
|
if (!nextProps.selectedKeys) {
|
||
|
// clear key nolonger existed
|
||
|
// clear checkedKeys according to targetKeys
|
||
|
const { dataSource, targetKeys = [] } = nextProps
|
||
|
|
||
|
const newSourceSelectedKeys = []
|
||
|
const newTargetSelectedKeys = []
|
||
|
dataSource.forEach(({ key }) => {
|
||
|
if (sourceSelectedKeys.includes(key) && !targetKeys.includes(key)) {
|
||
|
newSourceSelectedKeys.push(key)
|
||
|
}
|
||
|
if (targetSelectedKeys.includes(key) && targetKeys.includes(key)) {
|
||
|
newTargetSelectedKeys.push(key)
|
||
|
}
|
||
|
})
|
||
|
this.setState({
|
||
|
sourceSelectedKeys: newSourceSelectedKeys,
|
||
|
targetSelectedKeys: newTargetSelectedKeys,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (nextProps.selectedKeys) {
|
||
|
const targetKeys = nextProps.targetKeys || []
|
||
|
this.setState({
|
||
|
sourceSelectedKeys: nextProps.selectedKeys.filter(key => !targetKeys.includes(key)),
|
||
|
targetSelectedKeys: nextProps.selectedKeys.filter(key => targetKeys.includes(key)),
|
||
|
})
|
||
|
}
|
||
|
this.currentProps = { ...this.$props }
|
||
|
},
|
||
|
deep: true,
|
||
|
},
|
||
|
},
|
||
|
methods: {
|
||
|
splitDataSource (props) {
|
||
|
if (this.splitedDataSource) {
|
||
|
return this.splitedDataSource
|
||
|
}
|
||
|
|
||
|
const { dataSource, rowKey, targetKeys = [] } = props
|
||
|
|
||
|
const leftDataSource = []
|
||
|
const rightDataSource = new Array(targetKeys.length)
|
||
|
dataSource.forEach(record => {
|
||
|
if (rowKey) {
|
||
|
record.key = rowKey(record)
|
||
|
}
|
||
|
|
||
|
// rightDataSource should be ordered by targetKeys
|
||
|
// leftDataSource should be ordered by dataSource
|
||
|
const indexOfKey = targetKeys.indexOf(record.key)
|
||
|
if (indexOfKey !== -1) {
|
||
|
rightDataSource[indexOfKey] = record
|
||
|
} else {
|
||
|
leftDataSource.push(record)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
this.splitedDataSource = {
|
||
|
leftDataSource,
|
||
|
rightDataSource,
|
||
|
}
|
||
|
|
||
|
return this.splitedDataSource
|
||
|
},
|
||
|
|
||
|
moveTo (direction) {
|
||
|
const { targetKeys = [], dataSource = [] } = this.$props
|
||
|
const { sourceSelectedKeys, targetSelectedKeys } = this
|
||
|
const moveKeys = direction === 'right' ? sourceSelectedKeys : targetSelectedKeys
|
||
|
// filter the disabled options
|
||
|
const newMoveKeys = moveKeys.filter((key) =>
|
||
|
!dataSource.some(data => !!(key === data.key && data.disabled)),
|
||
|
)
|
||
|
// move items to target box
|
||
|
const newTargetKeys = direction === 'right'
|
||
|
? newMoveKeys.concat(targetKeys)
|
||
|
: targetKeys.filter(targetKey => newMoveKeys.indexOf(targetKey) === -1)
|
||
|
|
||
|
// empty checked keys
|
||
|
const oppositeDirection = direction === 'right' ? 'left' : 'right'
|
||
|
this.setState({
|
||
|
[this.getSelectedKeysName(oppositeDirection)]: [],
|
||
|
})
|
||
|
this.handleSelectChange(oppositeDirection, [])
|
||
|
|
||
|
this.$emit('change', newTargetKeys, direction, newMoveKeys)
|
||
|
},
|
||
|
moveToLeft () {
|
||
|
this.moveTo('left')
|
||
|
},
|
||
|
moveToRight () {
|
||
|
this.moveTo('right')
|
||
|
},
|
||
|
|
||
|
handleSelectChange (direction, holder) {
|
||
|
const { sourceSelectedKeys, targetSelectedKeys } = this
|
||
|
|
||
|
if (direction === 'left') {
|
||
|
this.$emit('selectChange', holder, targetSelectedKeys)
|
||
|
} else {
|
||
|
this.$emit('selectChange', sourceSelectedKeys, holder)
|
||
|
}
|
||
|
},
|
||
|
handleSelectAll (direction, filteredDataSource, checkAll) {
|
||
|
const originalSelectedKeys = this[this.getSelectedKeysName(direction)] || []
|
||
|
const currentKeys = filteredDataSource.map(item => item.key)
|
||
|
// Only operate current keys from original selected keys
|
||
|
const newKeys1 = originalSelectedKeys.filter((key) => currentKeys.indexOf(key) === -1)
|
||
|
const newKeys2 = [...originalSelectedKeys]
|
||
|
currentKeys.forEach((key) => {
|
||
|
if (newKeys2.indexOf(key) === -1) {
|
||
|
newKeys2.push(key)
|
||
|
}
|
||
|
})
|
||
|
const holder = checkAll ? newKeys1 : newKeys2
|
||
|
this.handleSelectChange(direction, holder)
|
||
|
|
||
|
if (!this.selectedKeys) {
|
||
|
this.setState({
|
||
|
[this.getSelectedKeysName(direction)]: holder,
|
||
|
})
|
||
|
}
|
||
|
},
|
||
|
|
||
|
handleLeftSelectAll (filteredDataSource, checkAll) {
|
||
|
this.handleSelectAll('left', filteredDataSource, checkAll)
|
||
|
},
|
||
|
handleRightSelectAll (filteredDataSource, checkAll) {
|
||
|
this.handleSelectAll('right', filteredDataSource, checkAll)
|
||
|
},
|
||
|
|
||
|
handleFilter (direction, e) {
|
||
|
this.setState({
|
||
|
// add filter
|
||
|
[`${direction}Filter`]: e.target.value,
|
||
|
})
|
||
|
this.$emit('searchChange', direction, e)
|
||
|
},
|
||
|
|
||
|
handleLeftFilter (e) {
|
||
|
this.handleFilter('left', e)
|
||
|
},
|
||
|
handleRightFilter (e) {
|
||
|
this.handleFilter('right', e)
|
||
|
},
|
||
|
|
||
|
handleClear (direction) {
|
||
|
this.setState({
|
||
|
[`${direction}Filter`]: '',
|
||
|
})
|
||
|
},
|
||
|
|
||
|
handleLeftClear () {
|
||
|
this.handleClear('left')
|
||
|
},
|
||
|
handleRightClear () {
|
||
|
this.handleClear('right')
|
||
|
},
|
||
|
|
||
|
handleSelect (direction, selectedItem, checked) {
|
||
|
const { sourceSelectedKeys, targetSelectedKeys } = this
|
||
|
const holder = direction === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys]
|
||
|
const index = holder.indexOf(selectedItem.key)
|
||
|
if (index > -1) {
|
||
|
holder.splice(index, 1)
|
||
|
}
|
||
|
if (checked) {
|
||
|
holder.push(selectedItem.key)
|
||
|
}
|
||
|
this.handleSelectChange(direction, holder)
|
||
|
|
||
|
if (!this.selectedKeys) {
|
||
|
this.setState({
|
||
|
[this.getSelectedKeysName(direction)]: holder,
|
||
|
})
|
||
|
}
|
||
|
},
|
||
|
|
||
|
handleLeftSelect (selectedItem, checked) {
|
||
|
return this.handleSelect('left', selectedItem, checked)
|
||
|
},
|
||
|
|
||
|
handleRightSelect (selectedItem, checked) {
|
||
|
return this.handleSelect('right', selectedItem, checked)
|
||
|
},
|
||
|
|
||
|
handleScroll (direction, e) {
|
||
|
this.$emit('scroll', direction, e)
|
||
|
},
|
||
|
|
||
|
handleLeftScroll (e) {
|
||
|
this.handleScroll('left', e)
|
||
|
},
|
||
|
handleRightScroll (e) {
|
||
|
this.handleScroll('right', e)
|
||
|
},
|
||
|
|
||
|
getTitles (transferLocale) {
|
||
|
if (this.titles) {
|
||
|
return this.titles
|
||
|
}
|
||
|
return transferLocale.titles
|
||
|
},
|
||
|
|
||
|
getSelectedKeysName (direction) {
|
||
|
return direction === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys'
|
||
|
},
|
||
|
|
||
|
renderTransfer (locale) {
|
||
|
const {
|
||
|
prefixCls = 'ant-transfer',
|
||
|
operations = [],
|
||
|
showSearch,
|
||
|
searchPlaceholder,
|
||
|
listStyle,
|
||
|
filterOption,
|
||
|
lazy,
|
||
|
render: renderItem,
|
||
|
} = getOptionProps(this)
|
||
|
const notFoundContent = getComponentFromProp(this, 'notFoundContent')
|
||
|
const { leftFilter, rightFilter, sourceSelectedKeys, targetSelectedKeys, $scopedSlots } = this
|
||
|
const { body, footer } = $scopedSlots
|
||
|
const { leftDataSource, rightDataSource } = this.splitDataSource(this.$props)
|
||
|
const leftActive = targetSelectedKeys.length > 0
|
||
|
const rightActive = sourceSelectedKeys.length > 0
|
||
|
|
||
|
const cls = classNames(prefixCls)
|
||
|
|
||
|
const titles = this.getTitles(locale)
|
||
|
return (
|
||
|
<div class={cls}>
|
||
|
<List
|
||
|
prefixCls={`${prefixCls}-list`}
|
||
|
titleText={titles[0]}
|
||
|
dataSource={leftDataSource}
|
||
|
filter={leftFilter}
|
||
|
filterOption={filterOption}
|
||
|
style={listStyle}
|
||
|
checkedKeys={sourceSelectedKeys}
|
||
|
handleFilter={this.handleLeftFilter}
|
||
|
handleClear={this.handleLeftClear}
|
||
|
handleSelect={this.handleLeftSelect}
|
||
|
handleSelectAll={this.handleLeftSelectAll}
|
||
|
renderItem={renderItem}
|
||
|
showSearch={showSearch}
|
||
|
searchPlaceholder={searchPlaceholder || locale.searchPlaceholder}
|
||
|
notFoundContent={notFoundContent || locale.notFoundContent}
|
||
|
itemUnit={locale.itemUnit}
|
||
|
itemsUnit={locale.itemsUnit}
|
||
|
body={body}
|
||
|
footer={footer}
|
||
|
lazy={lazy}
|
||
|
onScroll={this.handleLeftScroll}
|
||
|
/>
|
||
|
<Operation
|
||
|
class={`${prefixCls}-operation`}
|
||
|
rightActive={rightActive}
|
||
|
rightArrowText={operations[0]}
|
||
|
moveToRight={this.moveToRight}
|
||
|
leftActive={leftActive}
|
||
|
leftArrowText={operations[1]}
|
||
|
moveToLeft={this.moveToLeft}
|
||
|
/>
|
||
|
<List
|
||
|
prefixCls={`${prefixCls}-list`}
|
||
|
titleText={titles[1]}
|
||
|
dataSource={rightDataSource}
|
||
|
filter={rightFilter}
|
||
|
filterOption={filterOption}
|
||
|
style={listStyle}
|
||
|
checkedKeys={targetSelectedKeys}
|
||
|
handleFilter={this.handleRightFilter}
|
||
|
handleClear={this.handleRightClear}
|
||
|
handleSelect={this.handleRightSelect}
|
||
|
handleSelectAll={this.handleRightSelectAll}
|
||
|
renderItem={renderItem}
|
||
|
showSearch={showSearch}
|
||
|
searchPlaceholder={searchPlaceholder || locale.searchPlaceholder}
|
||
|
notFoundContent={notFoundContent || locale.notFoundContent}
|
||
|
itemUnit={locale.itemUnit}
|
||
|
itemsUnit={locale.itemsUnit}
|
||
|
body={body}
|
||
|
footer={footer}
|
||
|
lazy={lazy}
|
||
|
onScroll={this.handleRightScroll}
|
||
|
/>
|
||
|
</div>
|
||
|
)
|
||
|
},
|
||
|
},
|
||
|
render () {
|
||
|
return (
|
||
|
<LocaleReceiver
|
||
|
componentName='Transfer'
|
||
|
defaultLocale={defaultLocale.Transfer}
|
||
|
children={this.renderTransfer}
|
||
|
>
|
||
|
</LocaleReceiver>
|
||
|
)
|
||
|
},
|
||
|
}
|