I recently encountered a requirement that the contents of the shuttle box be a tree structure of data. The Transfer component that looks at elementUI does not support tree structure data and cannot be used directly. But el-Tree component support ah, that if let tree component and transfer component combine is not perfect 🤔.

To prepare

Clone a copy of Element’s source code and go to the package/ Tansfer/SRC folder, where the vue file is our target. Copy it to your project (copying both emitters. Js and ddY.js from mixins), and then modify it.

|-- components
|   |-- mixins
|   |   |-- emitter.js
|   |   |-- migraing.js
|   |-- main.vue
|   |-- transfer-panel.vue
Copy the code

use

Before using the main. Vue, transfer-panel. Vue two files for simple modification. Delete locale related code. Emitter. js and buy.js need to be reintroduced due to path changes. Then import main.vue in other components and you can use it (just like using El-Transfer directly).

<i-el-transfer v-model="value" :data="data"></i-el-transfer>

...

import iElTransfer from '.. /components/main'

export default {
    name: 'home',
    components: {
        iElTransfer
    },
    data () {
        return {
            data: [
                { key: 1, label: 'Option 1', disabled: false },
                { key: 2, label: 'Option 2', disabled: false}... ] }}... }Copy the code

transform

To convert the original single-layer structure into a tree structure, we already have an El-tree to use, so replace the area where the content is displayed with an El-Tree component.

# transfer-panel.vue
...
<el-checkbox-group
    v-model="checked"
    v-show=! "" hasNoMatch && data.length > 0"
    :class="{ 'is-filterable': filterable }"
    class="el-transfer-panel__list">
    <el-checkbox
        class="el-transfer-panel__item"
        :label="item[keyProp]"
        :disabled="item[disabledProp]"
        :key="item[keyProp]"
        v-for="item in filteredData">
        <option-content :option="item"></option-content>
    </el-checkbox>
</el-checkbox-group>.Copy the code

So anyway, let’s just replace it with el-tree

.<div v-show="data.length > 0"
    :class="{ 'is-filterable': filterable }"
    class="el-transfer-panel__list">

    <el-tree ref="tree" :data="filteredData"
        :node-key="keyProp"
        show-checkbox
        default-expand-all
        :default-checked-keys="checked">
    </el-tree>
</div>.Copy the code

Turn the data into a tree structure and go back to the page

That’s what we want, and it turns out to be the tree we want, but you can see the problem when you do it. Let’s keep looking at the code.

sourceData() {
    return this.data.filter(item= > this.value.indexOf(item[this.props.key]) === - 1);
},

targetData() {
    if (this.targetOrder === 'original') {
        return this.data.filter(item= > this.value.indexOf(item[this.props.key]) > - 1);
    } else {
        return this.value.reduce((arr, cur) = > {
            const val = this.dataObj[cur];
            if (val) {
                arr.push(val);
            }
        returnarr; } []); }},Copy the code

SourceData and targetData are used to show the data on the left and right sides respectively, and this.value is the data bound by the V-model when using the component. Value is used to filter out the left and right data. So let’s make a change here:

sourceData () {
    const data = deepCopy(this.data)

    // Children continue screening
    const filterData = (list) = > {
        return list.filter(item= > {
            if (item[this.props.children] && item[this.props.children].length > 0) {
                item[this.props.children] = filterData(item.children)
            }
            return this.value.indexOf(item[this.props.key]) === - 1})}return filterData(data)
},

targetData () {
    const data = deepCopy(this.data)

    const filterData = (list) = > {
        const res = []
        list.forEach(item= > {
            if (this.value.indexOf(item[this.props.key]) > - 1) {
                res.push(item)
            }

            if (item[this.props.children] && item[this.props.children].length > 0) {
                const result = filterData(item[this.props.children])
                if (result.length > 0) {
                    item[this.props.children] = result
                    const find = res.find(i= > i.key === item.key)

                    // no item found in res. Add item to res.
                    // Avoid problems when the parent element is not selected
                    if (find === undefined) {
                        res.push(item)
                    }
                }
            }
        })
        return res
    }

    return filterData(data)
},
Copy the code

I want the data added to the right here to have the same structure as the data on the left, so I remove the targetOrder option. Let’s try changing the value of the V-Model binding to see what happens.

I feel that way, but the left and right additions still don’t work. Let’s modify this part of the function. Go to the addToRight function, which filters out the data that the user selected on the left to add to the right and the data that already exists on the right, and modify this.value via $emit().

addToRight () {
    let currentValue = this.value.slice()
    const itemsToBeMoved = []
    const findSelectkey = (list) = > {
        const key = this.props.key
        const itemsToBeMoved = []

        list.forEach(item= > {
            const itemKey = item[key]
            if (
                this.leftChecked.indexOf(itemKey) > - 1 &&
                this.value.indexOf(itemKey) === - 1
            ) {
                itemsToBeMoved.push(itemKey)
            }

            if (item[this.props.children] && item[this.props.children].length > 0) { itemsToBeMoved.push(... findSelectkey(item[this.props.children]))
            }
        })
        returnitemsToBeMoved } itemsToBeMoved.push(... findSelectkey(this.data))

    currentValue = currentValue.concat(itemsToBeMoved)

    this.$emit('input', currentValue)
    this.$emit('change', currentValue, 'right'.this.leftChecked)
},

Copy the code

But the idea is the same, if there are children, we’re going to look for the underlying data. Then modify the addToLeft function.

addToLeft () {
    let currentValue = this.value.slice()
    const list = this.rightChecked.concat(this.rightFalfChecked)
    list.forEach(item= > {
        const index = currentValue.indexOf(item)
        if (index > - 1) {
            currentValue.splice(index, 1)}})this.$emit('input', currentValue)
    this.$emit('change', currentValue, 'left'.this.rightChecked)
},
Copy the code

What I’m thinking here is that if the element’s children are not selected, then the element is not selected, so I remove the element’s key from the right. A new rightFalfChecked is added to represent the right half-selected state.

The above changes to the main.vue file are almost done. We find that we also need to get the values of rightChecked, leftChecked, and rightFalfChecke. Modify the transfer-panel.vue file to get them. The El-Tree component has a check event that tells us which el-tree is currently selected.

Check: Triggered when the check box is clicked; There are two parameters: the object corresponding to the node in the array passed to the data property, and the current selected state object of the tree, including four properties checkedNodes, checkedKeys, halfCheckedNodes, and halfCheckedKeys

<el-tree ref="tree" :data="filteredData"
    :node-key="keyProp"
    show-checkbox
    default-expand-all
    :default-checked-keys="checked"
    @check="handleCheck">
</el-tree>

...
watch: {
    checked (val, oldVal) {
        this.updateAllChecked()
        if (this.checkChangeByUser) {
            const movedKeys = val.concat(oldVal)
                .filter(v => val.indexOf(v) === -1 || oldVal.indexOf(v) === -1)
            this.$emit('checked-change', val, movedKeys, this.halfChecked)
        } else {
            this.$emit('checked-change', val)
            this.checkChangeByUser = true}}}... handleCheck (cur, checkedInfo) { const { checkedKeys, halfCheckedKeys } = checkedInfo this.checked = checkedKeys this.halfChecked = halfCheckedKeys }Copy the code

conclusion

By now, the basic functions have been completed, but there are still some details, such as full selection, screening, etc., which will not be described here. Compared with the original Transfer component, the targetOrder and format attributes are removed here, while others are retained or modified. Hopefully this article has helped you, and you can view the full code at the link below. Welcome to 🎉️.

Making: link