diff --git a/dev/App.vue b/dev/App.vue
index e163fd7..9fd5020 100644
--- a/dev/App.vue
+++ b/dev/App.vue
@@ -1,159 +1,174 @@
-
-
-
- 📂
- +
- 📃
- ✂️
- 🍃
- 🌲
-
-
-
- {{newTree}}
-
-
+
+
+
+
+ 📂
+
+
+ +
+
+
+ 📃
+
+
+ ✂️
+
+
+ 🍃
+
+
+
+ {{ (slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded) ? '🌲' : '' }}
+
+
+
+
+ {{ newTree }}
+
+
diff --git a/readme.md b/readme.md
index 382c761..002bc9c 100644
--- a/readme.md
+++ b/readme.md
@@ -199,13 +199,26 @@ drop-before | {node, src, target} | Trigger after dropping a node before another
drop-after | {node, src, target} | Trigger after dropping a node after another. node: the draggable node, src: the draggable node's parent, target: the node that draggable node will drop after
# customize operation icons
-The component has default icons for `addTreeNodeIcon`, `addLeafNodeIcon`, `editNodeIcon`, `delNodeIcon`, `leafNodeIcon`, `treeNodeIcon` button, but you can also customize them:
+The component has default icons for `addTreeNodeIcon`, `addLeafNodeIcon`, `editNodeIcon`, `delNodeIcon`, `leafNodeIcon`, `treeNodeIcon` button, but you can also customize them and can access `model`, `root`, `expanded` as below:
```html
- 📂
- +
- 📃
- ✂️
- 🍃
- 🌲
+
+ 📂
+
+
+ +
+
+
+ 📃
+
+
+ ✂️
+
+
+ 🍃
+
+
+
+ {{ (slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded) ? '🌲' : '' }}
+
```
diff --git a/src/Tree.js b/src/Tree.js
index facc879..902484b 100644
--- a/src/Tree.js
+++ b/src/Tree.js
@@ -1,3 +1,4 @@
+import {traverseTree} from './tools'
/**
* Tree data struct
* Created by ayou on 2017/7/20.
@@ -8,155 +9,160 @@
* dragDisabled: decide if it can be dragged
* disabled: desabled all operation
*/
-const TreeNode = function (data) {
- const { id, isLeaf } = data
- this.id = (typeof id === 'undefined') ? new Date().valueOf() : id
- this.parent = null
- this.children = null
- this.isLeaf = !!isLeaf
+export class TreeNode {
+ constructor(data) {
+ const {id, isLeaf} = data
+ this.id = typeof id === 'undefined' ? new Date().valueOf() : id
+ this.parent = null
+ this.children = null
+ this.isLeaf = !!isLeaf
- // other params
- for (var k in data) {
- if (k !== 'id' && k !== 'children' && k !== 'isLeaf') {
- this[k] = data[k]
+ // other params
+ for (var k in data) {
+ if (k !== 'id' && k !== 'children' && k !== 'isLeaf') {
+ this[k] = data[k]
+ }
}
}
-}
-TreeNode.prototype.changeName = function (name) {
- this.name = name
-}
-
-TreeNode.prototype.addChildren = function (children) {
- if (!this.children) {
- this.children = []
+ changeName(name) {
+ this.name = name
}
- if (Array.isArray(children)) {
- for (let i = 0, len = children.length; i < len; i++) {
- const child = children[i]
+ addChildren(children) {
+ if (!this.children) {
+ this.children = []
+ }
+
+ if (Array.isArray(children)) {
+ for (let i = 0, len = children.length; i < len; i++) {
+ const child = children[i]
+ child.parent = this
+ child.pid = this.id
+ }
+ this.children.concat(children)
+ } else {
+ const child = children
child.parent = this
child.pid = this.id
- }
- this.children.concat(children)
- } else {
- const child = children
- child.parent = this
- child.pid = this.id
- this.children.push(child)
- }
-}
-
-// remove self
-TreeNode.prototype.remove = function () {
- const parent = this.parent
- const index = parent.findChildIndex(this)
- parent.children.splice(index, 1)
-}
-
-// remove child
-TreeNode.prototype._removeChild = function (child) {
- for (var i = 0, len = this.children.length; i < len; i++) {
- if (this.children[i] === child) {
- this.children.splice(i, 1)
- break
+ this.children.push(child)
}
}
-}
-TreeNode.prototype.isTargetChild = function (target) {
- let parent = target.parent
- while (parent) {
- if (parent === this) {
- return true
- }
- parent = parent.parent
- }
- return false
-}
-
-TreeNode.prototype.moveInto = function (target) {
- if (this.name === 'root' || this === target) {
- return
+ // remove self
+ remove() {
+ const parent = this.parent
+ const index = parent.findChildIndex(this)
+ parent.children.splice(index, 1)
}
- // cannot move ancestor to child
- if (this.isTargetChild(target)) {
- return
- }
-
- // cannot move to leaf node
- if (target.isLeaf) {
- return
- }
-
- this.parent._removeChild(this)
- this.parent = target
- this.pid = target.id
- if (!target.children) {
- target.children = []
- }
- target.children.unshift(this)
-}
-
-TreeNode.prototype.findChildIndex = function (child) {
- var index
- for (let i = 0, len = this.children.length; i < len; i++) {
- if (this.children[i] === child) {
- index = i
- break
+ // remove child
+ _removeChild(child) {
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ if (this.children[i] === child) {
+ this.children.splice(i, 1)
+ break
+ }
}
}
- return index
-}
-TreeNode.prototype._beforeInsert = function (target) {
- if (this.name === 'root' || this === target) {
+ isTargetChild(target) {
+ let parent = target.parent
+ while (parent) {
+ if (parent === this) {
+ return true
+ }
+ parent = parent.parent
+ }
return false
}
- // cannot insert ancestor to child
- if (this.isTargetChild(target)) {
- return false
- }
-
- this.parent._removeChild(this)
- this.parent = target.parent
- this.pid = target.parent.id
- return true
-}
-
-TreeNode.prototype.insertBefore = function (target) {
- if (!this._beforeInsert(target)) return
-
- const pos = target.parent.findChildIndex(target)
- target.parent.children.splice(pos, 0, this)
-}
-
-TreeNode.prototype.insertAfter = function (target) {
- if (!this._beforeInsert(target)) return
-
- const pos = target.parent.findChildIndex(target)
- target.parent.children.splice(pos + 1, 0, this)
-}
-
-function Tree (data) {
- this.root = new TreeNode({ name: 'root', isLeaf: false, id: 0 })
- this.initNode(this.root, data)
- return this.root
-}
-
-Tree.prototype.initNode = function (node, data) {
- for (let i = 0, len = data.length; i < len; i++) {
- var _data = data[i]
-
- var child = new TreeNode(_data)
- if (_data.children && _data.children.length > 0) {
- this.initNode(child, _data.children)
+ moveInto(target) {
+ if (this.name === 'root' || this === target) {
+ return
}
- node.addChildren(child)
+
+ // cannot move ancestor to child
+ if (this.isTargetChild(target)) {
+ return
+ }
+
+ // cannot move to leaf node
+ if (target.isLeaf) {
+ return
+ }
+
+ this.parent._removeChild(this)
+ this.parent = target
+ this.pid = target.id
+ if (!target.children) {
+ target.children = []
+ }
+ target.children.unshift(this)
+ }
+
+ findChildIndex(child) {
+ var index
+ for (let i = 0, len = this.children.length; i < len; i++) {
+ if (this.children[i] === child) {
+ index = i
+ break
+ }
+ }
+ return index
+ }
+
+ _canInsert(target) {
+ if (this.name === 'root' || this === target) {
+ return false
+ }
+
+ // cannot insert ancestor to child
+ if (this.isTargetChild(target)) {
+ return false
+ }
+
+ this.parent._removeChild(this)
+ this.parent = target.parent
+ this.pid = target.parent.id
+ return true
+ }
+
+ insertBefore(target) {
+ if (!this._canInsert(target)) return
+
+ const pos = target.parent.findChildIndex(target)
+ target.parent.children.splice(pos, 0, this)
+ }
+
+ insertAfter(target) {
+ if (!this._canInsert(target)) return
+
+ const pos = target.parent.findChildIndex(target)
+ target.parent.children.splice(pos + 1, 0, this)
+ }
+
+ toString() {
+ return JSON.stringify(traverseTree(this))
}
}
-exports.Tree = Tree
-exports.TreeNode = TreeNode
+export class Tree {
+ constructor(data) {
+ this.root = new TreeNode({name: 'root', isLeaf: false, id: 0})
+ this.initNode(this.root, data)
+ return this.root
+ }
+
+ initNode(node, data) {
+ for (let i = 0, len = data.length; i < len; i++) {
+ var _data = data[i]
+
+ var child = new TreeNode(_data)
+ if (_data.children && _data.children.length > 0) {
+ this.initNode(child, _data.children)
+ }
+ node.addChildren(child)
+ }
+ }
+}
diff --git a/src/VueTreeList.vue b/src/VueTreeList.vue
index 9cb5cca..100b34d 100644
--- a/src/VueTreeList.vue
+++ b/src/VueTreeList.vue
@@ -1,422 +1,510 @@
-
+
-
-
+
-
+ @dragover="dragOverBottom"
+ @dragleave="dragLeaveBottom"
+ >
-
- -
+
-
-
-
-
-
-
-
-
-
+ :key="model.id"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tools.js b/src/tools.js
index fc0524d..024a5e4 100644
--- a/src/tools.js
+++ b/src/tools.js
@@ -4,7 +4,7 @@
var handlerCache
-exports.addHandler = function (element, type, handler) {
+export const addHandler = function (element, type, handler) {
handlerCache = handler
if (element.addEventListener) {
element.addEventListener(type, handler, false)
@@ -15,7 +15,7 @@ exports.addHandler = function (element, type, handler) {
}
}
-exports.removeHandler = function (element, type) {
+export const removeHandler = function (element, type) {
if (element.removeEventListener) {
element.removeEventListener(type, handlerCache, false)
} else if (element.detachEvent) {
@@ -25,7 +25,21 @@ exports.removeHandler = function (element, type) {
}
}
-// exports.fireFocusEvent = function (ele) {
-// var event = new FocusEvent()
-// ele.dispatch(event)
-// }
+// depth first search
+export const traverseTree = (root) => {
+ var newRoot = {}
+
+ for (var k in root) {
+ if (k !== 'children' && k !== 'parent') {
+ newRoot[k] = root[k]
+ }
+ }
+
+ if (root.children && root.children.length > 0) {
+ newRoot.children = []
+ for (var i = 0, len = root.children.length; i < len; i++) {
+ newRoot.children.push(traverseTree(root.children[i]))
+ }
+ }
+ return newRoot
+}
diff --git a/tests/unit/__snapshots__/slot.spec.js.snap b/tests/unit/__snapshots__/slot.spec.js.snap
new file mode 100644
index 0000000..784d2fc
--- /dev/null
+++ b/tests/unit/__snapshots__/slot.spec.js.snap
@@ -0,0 +1,53 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Slot render slot correctly 1`] = `
+
+
+
+
+
+
+
+
+
+
+
🍃
+
+ Node 1-1
+
+
+
+ 📃 ✂️
+
+
+
+
+
+
+
+
+
+
+
+
❀
+
+ Node 2
+
+
📂 + 📃 ✂️
+
+
+
+
+
+
+
+`;
diff --git a/tests/unit/slot.spec.js b/tests/unit/slot.spec.js
new file mode 100644
index 0000000..6cdd585
--- /dev/null
+++ b/tests/unit/slot.spec.js
@@ -0,0 +1,79 @@
+import Vue from 'vue'
+import {mount} from '@vue/test-utils'
+import {Tree, VueTreeList} from '@/index'
+
+describe('Slot', () => {
+ let wrapper
+
+ beforeEach(() => {
+ const tree = new Tree([
+ {
+ name: 'Node 1',
+ id: 't1',
+ pid: 0,
+ children: [
+ {
+ name: 'Node 1-1',
+ id: 't11',
+ isLeaf: true,
+ pid: 't1'
+ }
+ ]
+ },
+ {
+ name: 'Node 2',
+ id: 't2',
+ pid: 0
+ }
+ ])
+ wrapper = mount(VueTreeList, {
+ propsData: {model: new Tree([])},
+ scopedSlots: {
+ addTreeNodeIcon() {
+ return 📂
+ },
+ addLeafNodeIcon() {
+ return +
+ },
+ editNodeIcon() {
+ return 📃
+ },
+ delNodeIcon(slotProps) {
+ return (slotProps.model.isLeaf || !slotProps.model.children) ? ✂️ :
+ },
+ leafNodeIcon() {
+ return 🍃
+ },
+ treeNodeIcon(slotProps) {
+ return { slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded ? '🌲' : '❀' }
+ }
+ }
+ })
+ wrapper.setProps({model: tree})
+ })
+
+ it('render slot correctly', () => {
+ expect(wrapper).toMatchSnapshot()
+ })
+
+ it('toggle tree node show different icon', done => {
+ const $caretDown = wrapper.find('.vtl-icon-caret-down')
+ expect(wrapper.find('#t1 .tree-node-icon').text()).toBe('❀')
+ $caretDown.trigger('click')
+ Vue.nextTick(() => {
+ expect(wrapper.exists('.vtl-icon-caret-right')).toBe(true)
+ expect(wrapper.find('#t1 .tree-node-icon').text()).toBe('🌲')
+ done()
+ })
+ })
+
+ it('dont show ✂️ after add child ', done => {
+ const $addTreeNodeIcon = wrapper.find('#t2 .add-tree-node-icon')
+ expect(wrapper.find('#t2 .del-node-icon').exists()).toBe(true)
+ $addTreeNodeIcon.trigger('click')
+ Vue.nextTick(() => {
+ expect(wrapper.find('#t2 .del-node-icon').exists()).toBe(false)
+ done()
+ })
+ })
+})