์ด ํ๋ก์ ํธ๋ ๋๋๊ทธ๊ฐ ๊ฐ๋ฅํ ํธ๋ฆฌ ์ปดํฌ๋ํธ์ ๋๋ค. ์ด ์ปดํฌ๋ํธ๋ css๋ฅผ ํฌํจํ์ง ์์ผ๋ฉฐ, ๋ฐ๋ชจ๋ฅผ ์ฌ์ฉํ๊ธฐ์ํด ๋น์ ์ style์ ์ถ๊ฐํ ํ์๊ฐ ์์ต๋๋ค. ๋ฐ๋ชจ์ style์ ์ด๋ ต์ง ์์ต๋๋ค.
ํฐ์น๋ฅผ ์ง์ํฉ๋๋ค (1๊ฐ์ ์์ดํ )
ํ์ดํ | ์๋ฆฌํ์ด | ์์ฑ
npm i vue-draggable-nested-tree
import {DraggableTree} from 'vue-draggable-nested-tree'
// vue-draggable-nested-tree contains Tree, TreeNode, DraggableTree, DraggableTreeNode
// import the component and register it as global or local component
data: [
{text: 'node 1'},
{text: 'node 2'},
{text: 'node 3 undraggable', draggable: false},
{text: 'node 4'},
{text: 'node 4 undroppable', droppable: false},
{text: 'node 5', children: [
{text: 'node 1'},
{text: 'node 2', children: [
{text: 'node 3'},
{text: 'node 4'},
]},
{text: 'node 2 undroppable', droppable: false, children: [
{text: 'node 3'},
{text: 'node 4'},
]},
{text: 'node 2', children: [
{text: 'node 3'},
{text: 'node 4 undroppable', droppable: false},
]},
{text: 'node 3'},
{text: 'node 4'},
{text: 'node 3'},
{text: 'node 4'},
{text: 'node 3'},
{text: 'node 4'},
{text: 'node 3'},
{text: 'node 4'},
]},
]
Tree(:data="data" draggable crossTree)
div(slot-scope="{data, store, vm}")
//- data is node
//- store is the tree
//- vm is node Vue instance, you can get node level by vm.level
template(v-if="!data.isDragPlaceHolder")
b(v-if="data.children && data.children.length" @click="store.toggleOpen(data)") {{data.open ? '-' : '+'}}
span {{data.text}}
//- slot-scope="{data, store, vm}" may not work in old browsers, replace with slot-scope="slot"
Tree(:data="data" draggable crossTree)
div(slot-scope="slot")
//- data is node
//- store is the tree
//- vm is node Vue instance, you can get node level by vm.level
template(v-if="!slot.data.isDragPlaceHolder")
b(v-if="slot.data.children && slot.data.children.length" @click="slot.store.toggleOpen(slot.data)") {{slot.data.open ? '-' : '+'}}
span {{slot.data.text}}
'store'๋ tree์ ์๋ VM์ ๋๋ค.
// base tree
data: {}, // type Array
indent: {default: 16},
activatedClass: {default: 'active'},
openedClass: {default: 'open'},
space: {default: 10}, // space between node, unit px
// draggable tree
preventSelect: {default: true}, // if to prevent drag handler text be selected when drag, excluding input and textarea
getTriggerEl: {type: Function}, // get the el trigger drag, default is node self. arguments(nodeVm)
draggable: {}, // is the tree draggable, default false
droppable: {default: true}, // is the tree droppable, default true
crossTree: {}, // can a node of the tree be dragged into other tree, or receive other tree node
ondragstart: {type: Function}, // hook. return false to prevent drag. arguments(node, draggableHelperInfo)
ondragend: {type: Function}, // hook. return false to prevent drop. arguments(node, draggableHelperInfo)
// base
rootData, // generated by tree
// draggable
dplh, // drag placeholder. globally unique.
trees, // array, all trees in the app. globally unique.
// store is the tree vm
drag(node), // on drag start.
drop(node, targetTree, oldTree), // after drop.
change(node, targetTree, oldTree), // after drop, only when the node position changed
nodeOpenChanged(node) // on a node is closed or open
- targetTree์ oldTree ๋ tree vm์ ๋๋ค.
- oldTree is ์ค์ง cross tree์์๋ง ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค. ๊ทธ ์ธ์๋ null์ ๋๋ค..
- ๋ง์ฝ cross tree๋ฅผ ์ฌ์ฉ์ค์ด๋ผ๋ฉด targetTree์ oldTree ๋ ๋๋กญํ ๋ ๊ฐ์ด ๋ฐ๋๊ฒ์ ๋๋ค.
pure(node, withChildren, after)
/*
pure
return a node data without runtime properties.(!: property which starts with '_' will be removed)
withChildren: optional. after: Function, optional
the code about after(t is computed node data):
if (after) {
return after(t, node) || t
}
return t
*/
getNodeById(id)
getActivated()
getOpened()
activeNode(node, inactiveOld)
toggleActive(node, inactiveOld)
openNode(node, closeOld)
toggleOpen(node, closeOld)
// follow methods are easy, so I paste their soure code
getPureData(after) { return this.pure(this.rootData, true, after).children } // after: Function, optional
deleteNode(node) { return hp.arrayRemove(node.parent.children, node) }
// add node: like array. eg: node.children.push(newNodeData)
// update node: just assign to the node properties directly
isNodeDraggable(node)
isNodeDroppable(node)
// base
_id
_vm
parent
children: [],
open,
active: false,
style: {},
class: '',
innerStyle: {},
innerClass: '',
innerBackStyle: {},
innerBackClass: {},
// draggable
draggable // default true. Please check 'draggable & droppable' below
droppable // default true. Please check 'draggable & droppable' below
isDragPlaceHolder
node._vm // vm
node._vm.level // ๋
ธ๋ ๋ ๋ฒจ
node._vm.store // ํธ๋ฆฌ
node.parent._vm // ๋ถ๋ชจ node vm
node._vm.store
.he-tree{
border: 1px solid #ccc;
padding: 20px;
width: 300px;
}
.tree-node{
}
.tree-node-inner{
padding: 5px;
border: 1px solid #ccc;
cursor: pointer;
}
.draggable-placeholder{
}
.draggable-placeholder-inner{
border: 1px dashed #0088F8;
box-sizing: border-box;
background: rgba(0, 136, 249, 0.09);
color: #0088f9;
text-align: center;
padding: 0;
display: flex;
align-items: center;
}
์ด ํจํค์ง๋ฅผ cloneํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ,
npm install
npm run dev
ํ๊ฐ์ node๋ ๊ธฐ๋ณธ์ผ๋ก ๋๋๊ทธ&๋๋์ด ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค. ๋น์ ์ node์ ์์ฑ์ ๋๋๊ทธ&๋๋์ผ๋ก ์ค์ ๊ฐ๋ฅํฉ๋๋ค. ๋ค๋ฅธ๋ฐฉ๋ฒ์ผ๋ก๋ 'drag'์ด๋ฒคํธ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ค๊ฐ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๋๋๊ทธ ํน์ ๋๋์์ฑ์ผ๋ก ์ธํ ํ๋๊ฒ์ ๋๋ค.
๋์ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ tree-helper๋ฅผ ์ถ์ฒํ๋ค.. ๊ทธ๊ฒ์ 2๊ฐ์ง์ ๊ฐ๋กํธ๋ฆฌ๋ฅผ ์ง์ํ๋ค: depthFirstSearch, breadthFirstSearch.
draggable-helper ๋๋๊ทธ๋ฅผ ์ํ ์ ๊ฐ ๊ฐ๋ฐํ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.. ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ ์ด ์ปดํฌ๋ํธ๋ฅผ ํตํด ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๋๋๊ทธ ํจ์๋ฅผ ์ฌ์ฉํ ๋ ๋์์ด ๋ ๊ฒ์ ๋๋ค.