Compare commits
10 Commits
feature-ad
...
bugfix/sty
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d8a5da1e0e | ||
![]() |
780d42c6ea | ||
![]() |
9c2d25e313 | ||
![]() |
fbd370e9e5 | ||
![]() |
332402dee6 | ||
![]() |
61ae848898 | ||
![]() |
82e87e493a | ||
![]() |
2427f47201 | ||
![]() |
fab494cedf | ||
![]() |
ad6ad1b255 |
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 ayou
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
31
dev/App.vue
31
dev/App.vue
@@ -12,18 +12,33 @@
|
|||||||
:model="data"
|
:model="data"
|
||||||
default-tree-node-name="new node"
|
default-tree-node-name="new node"
|
||||||
default-leaf-node-name="new leaf"
|
default-leaf-node-name="new leaf"
|
||||||
v-bind:default-expanded="false">
|
v-bind:default-expanded="false"
|
||||||
<span class="icon" slot="addTreeNodeIcon">📂</span>
|
>
|
||||||
<span class="icon" slot="addLeafNodeIcon">+</span>
|
<template v-slot:addTreeNodeIcon="slotProps">
|
||||||
<span class="icon" slot="editNodeIcon">📃</span>
|
<span class="icon">📂</span>
|
||||||
<span class="icon" slot="delNodeIcon">✂️</span>
|
</template>
|
||||||
<span class="icon" slot="leafNodeIcon">🍃</span>
|
<template v-slot:addLeafNodeIcon="slotProps">
|
||||||
<span class="icon" slot="treeNodeIcon">🌲</span>
|
<span class="icon">+</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:editNodeIcon="slotProps">
|
||||||
|
<span class="icon">📃</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:delNodeIcon="slotProps">
|
||||||
|
<span class="icon">✂️</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:leafNodeIcon="slotProps">
|
||||||
|
<span class="icon">🍃</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:treeNodeIcon="slotProps">
|
||||||
|
<span class="icon">
|
||||||
|
{{ (slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded) ? '🌲' : '' }}</span>
|
||||||
|
</template>
|
||||||
</vue-tree-list>
|
</vue-tree-list>
|
||||||
<button @click="getNewTree">Get new tree</button>
|
<button @click="getNewTree">Get new tree</button>
|
||||||
<pre>
|
<pre>
|
||||||
{{ newTree }}
|
{{ newTree }}
|
||||||
</pre>
|
</pre
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-tree-list",
|
"name": "vue-tree-list",
|
||||||
"version": "1.4.1",
|
"version": "1.4.2",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-tree-list",
|
"name": "vue-tree-list",
|
||||||
"version": "1.4.1",
|
"version": "1.4.2",
|
||||||
"description": "A vue component for tree structure. Support adding treenode/leafnode, editing node's name and dragging.",
|
"description": "A vue component for tree structure. Support adding treenode/leafnode, editing node's name and dragging.",
|
||||||
"author": "ayou",
|
"author": "ayou",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
27
readme.md
27
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
|
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
|
# 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
|
```html
|
||||||
<span class="icon" slot="addTreeNodeIcon">📂</span>
|
<template v-slot:addTreeNodeIcon="slotProps">
|
||||||
<span class="icon" slot="addLeafNodeIcon">+</span>
|
<span class="icon">📂</span>
|
||||||
<span class="icon" slot="editNodeIcon">📃</span>
|
</template>
|
||||||
<span class="icon" slot="delNodeIcon">✂️</span>
|
<template v-slot:addLeafNodeIcon="slotProps">
|
||||||
<span class="icon" slot="leafNodeIcon">🍃</span>
|
<span class="icon">+</span>
|
||||||
<span class="icon" slot="treeNodeIcon">🌲</span>
|
</template>
|
||||||
|
<template v-slot:editNodeIcon="slotProps">
|
||||||
|
<span class="icon">📃</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:delNodeIcon="slotProps">
|
||||||
|
<span class="icon">✂️</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:leafNodeIcon="slotProps">
|
||||||
|
<span class="icon">🍃</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:treeNodeIcon="slotProps">
|
||||||
|
<span class="icon">
|
||||||
|
{{ (slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded) ? '🌲' : '' }}</span>
|
||||||
|
</template>
|
||||||
```
|
```
|
||||||
|
44
src/Tree.js
44
src/Tree.js
@@ -1,3 +1,4 @@
|
|||||||
|
import {traverseTree} from './tools'
|
||||||
/**
|
/**
|
||||||
* Tree data struct
|
* Tree data struct
|
||||||
* Created by ayou on 2017/7/20.
|
* Created by ayou on 2017/7/20.
|
||||||
@@ -8,9 +9,10 @@
|
|||||||
* dragDisabled: decide if it can be dragged
|
* dragDisabled: decide if it can be dragged
|
||||||
* disabled: desabled all operation
|
* disabled: desabled all operation
|
||||||
*/
|
*/
|
||||||
const TreeNode = function (data) {
|
export class TreeNode {
|
||||||
|
constructor(data) {
|
||||||
const {id, isLeaf} = data
|
const {id, isLeaf} = data
|
||||||
this.id = (typeof id === 'undefined') ? new Date().valueOf() : id
|
this.id = typeof id === 'undefined' ? new Date().valueOf() : id
|
||||||
this.parent = null
|
this.parent = null
|
||||||
this.children = null
|
this.children = null
|
||||||
this.isLeaf = !!isLeaf
|
this.isLeaf = !!isLeaf
|
||||||
@@ -23,11 +25,11 @@ const TreeNode = function (data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.changeName = function (name) {
|
changeName(name) {
|
||||||
this.name = name
|
this.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.addChildren = function (children) {
|
addChildren(children) {
|
||||||
if (!this.children) {
|
if (!this.children) {
|
||||||
this.children = []
|
this.children = []
|
||||||
}
|
}
|
||||||
@@ -48,14 +50,14 @@ TreeNode.prototype.addChildren = function (children) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove self
|
// remove self
|
||||||
TreeNode.prototype.remove = function () {
|
remove() {
|
||||||
const parent = this.parent
|
const parent = this.parent
|
||||||
const index = parent.findChildIndex(this)
|
const index = parent.findChildIndex(this)
|
||||||
parent.children.splice(index, 1)
|
parent.children.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove child
|
// remove child
|
||||||
TreeNode.prototype._removeChild = function (child) {
|
_removeChild(child) {
|
||||||
for (var i = 0, len = this.children.length; i < len; i++) {
|
for (var i = 0, len = this.children.length; i < len; i++) {
|
||||||
if (this.children[i] === child) {
|
if (this.children[i] === child) {
|
||||||
this.children.splice(i, 1)
|
this.children.splice(i, 1)
|
||||||
@@ -64,7 +66,7 @@ TreeNode.prototype._removeChild = function (child) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.isTargetChild = function (target) {
|
isTargetChild(target) {
|
||||||
let parent = target.parent
|
let parent = target.parent
|
||||||
while (parent) {
|
while (parent) {
|
||||||
if (parent === this) {
|
if (parent === this) {
|
||||||
@@ -75,7 +77,7 @@ TreeNode.prototype.isTargetChild = function (target) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.moveInto = function (target) {
|
moveInto(target) {
|
||||||
if (this.name === 'root' || this === target) {
|
if (this.name === 'root' || this === target) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -99,7 +101,7 @@ TreeNode.prototype.moveInto = function (target) {
|
|||||||
target.children.unshift(this)
|
target.children.unshift(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.findChildIndex = function (child) {
|
findChildIndex(child) {
|
||||||
var index
|
var index
|
||||||
for (let i = 0, len = this.children.length; i < len; i++) {
|
for (let i = 0, len = this.children.length; i < len; i++) {
|
||||||
if (this.children[i] === child) {
|
if (this.children[i] === child) {
|
||||||
@@ -110,7 +112,7 @@ TreeNode.prototype.findChildIndex = function (child) {
|
|||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype._beforeInsert = function (target) {
|
_canInsert(target) {
|
||||||
if (this.name === 'root' || this === target) {
|
if (this.name === 'root' || this === target) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -126,27 +128,33 @@ TreeNode.prototype._beforeInsert = function (target) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.insertBefore = function (target) {
|
insertBefore(target) {
|
||||||
if (!this._beforeInsert(target)) return
|
if (!this._canInsert(target)) return
|
||||||
|
|
||||||
const pos = target.parent.findChildIndex(target)
|
const pos = target.parent.findChildIndex(target)
|
||||||
target.parent.children.splice(pos, 0, this)
|
target.parent.children.splice(pos, 0, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.insertAfter = function (target) {
|
insertAfter(target) {
|
||||||
if (!this._beforeInsert(target)) return
|
if (!this._canInsert(target)) return
|
||||||
|
|
||||||
const pos = target.parent.findChildIndex(target)
|
const pos = target.parent.findChildIndex(target)
|
||||||
target.parent.children.splice(pos + 1, 0, this)
|
target.parent.children.splice(pos + 1, 0, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tree (data) {
|
toString() {
|
||||||
|
return JSON.stringify(traverseTree(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Tree {
|
||||||
|
constructor(data) {
|
||||||
this.root = new TreeNode({name: 'root', isLeaf: false, id: 0})
|
this.root = new TreeNode({name: 'root', isLeaf: false, id: 0})
|
||||||
this.initNode(this.root, data)
|
this.initNode(this.root, data)
|
||||||
return this.root
|
return this.root
|
||||||
}
|
}
|
||||||
|
|
||||||
Tree.prototype.initNode = function (node, data) {
|
initNode(node, data) {
|
||||||
for (let i = 0, len = data.length; i < len; i++) {
|
for (let i = 0, len = data.length; i < len; i++) {
|
||||||
var _data = data[i]
|
var _data = data[i]
|
||||||
|
|
||||||
@@ -157,6 +165,4 @@ Tree.prototype.initNode = function (node, data) {
|
|||||||
node.addChildren(child)
|
node.addChildren(child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
exports.Tree = Tree
|
|
||||||
exports.TreeNode = TreeNode
|
|
||||||
|
@@ -1,38 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class='vtl'>
|
<div class="vtl">
|
||||||
<div
|
<div
|
||||||
v-if="model.name !== 'root'"
|
v-if="model.name !== 'root'"
|
||||||
:id='model.id'
|
:id="model.id"
|
||||||
class="vtl-node"
|
class="vtl-node"
|
||||||
:class="{'vtl-leaf-node': model.isLeaf, 'vtl-tree-node': !model.isLeaf}"
|
:class="{'vtl-leaf-node': model.isLeaf, 'vtl-tree-node': !model.isLeaf}"
|
||||||
>
|
>
|
||||||
<div class="vtl-border vtl-up" :class="{'vtl-active': isDragEnterUp}"
|
<div
|
||||||
|
class="vtl-border vtl-up"
|
||||||
|
:class="{'vtl-active': isDragEnterUp}"
|
||||||
@drop="dropBefore"
|
@drop="dropBefore"
|
||||||
@dragenter="dragEnterUp"
|
@dragenter="dragEnterUp"
|
||||||
@dragover='dragOverUp'
|
@dragover="dragOverUp"
|
||||||
@dragleave="dragLeaveUp" />
|
@dragleave="dragLeaveUp"
|
||||||
<div :class="treeNodeClass"
|
/>
|
||||||
|
<div
|
||||||
|
:class="treeNodeClass"
|
||||||
:draggable="!model.dragDisabled"
|
:draggable="!model.dragDisabled"
|
||||||
@dragstart='dragStart'
|
@dragstart="dragStart"
|
||||||
@dragover='dragOver'
|
@dragover="dragOver"
|
||||||
@dragenter='dragEnter'
|
@dragenter="dragEnter"
|
||||||
@dragleave='dragLeave'
|
@dragleave="dragLeave"
|
||||||
@drop='drop'
|
@drop="drop"
|
||||||
@dragend='dragEnd'
|
@dragend="dragEnd"
|
||||||
@mouseover='mouseOver'
|
@mouseover="mouseOver"
|
||||||
@mouseout='mouseOut'
|
@mouseout="mouseOut"
|
||||||
@click.stop='click'>
|
@click.stop="click"
|
||||||
<span class="vtl-caret vtl-is-small" v-if="model.children && model.children.length > 0">
|
>
|
||||||
<i class="vtl-icon" :class="caretClass" @click.prevent.stop="toggle"></i>
|
<span
|
||||||
|
class="vtl-caret vtl-is-small"
|
||||||
|
v-if="model.children && model.children.length > 0"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="vtl-icon"
|
||||||
|
:class="caretClass"
|
||||||
|
@click.prevent.stop="toggle"
|
||||||
|
></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="model.isLeaf">
|
<span v-if="model.isLeaf">
|
||||||
<slot name="leafNodeIcon">
|
<slot
|
||||||
|
name="leafNodeIcon"
|
||||||
|
:expanded="expanded"
|
||||||
|
:model="model"
|
||||||
|
:root="rootNode"
|
||||||
|
>
|
||||||
<i class="vtl-icon vtl-menu-icon vtl-icon-file"></i>
|
<i class="vtl-icon vtl-menu-icon vtl-icon-file"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<slot name="treeNodeIcon">
|
<slot
|
||||||
|
name="treeNodeIcon"
|
||||||
|
:expanded="expanded"
|
||||||
|
:model="model"
|
||||||
|
:root="rootNode"
|
||||||
|
>
|
||||||
<i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i>
|
<i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
@@ -40,53 +62,117 @@
|
|||||||
<div class="vtl-node-content" v-if="!editable">
|
<div class="vtl-node-content" v-if="!editable">
|
||||||
{{ model.name }}
|
{{ model.name }}
|
||||||
</div>
|
</div>
|
||||||
<input v-else class="vtl-input" type="text" ref="nodeInput" :value="model.name" @input="updateName" @blur="setUnEditable">
|
<input
|
||||||
|
v-else
|
||||||
|
class="vtl-input"
|
||||||
|
type="text"
|
||||||
|
ref="nodeInput"
|
||||||
|
:value="model.name"
|
||||||
|
@input="updateName"
|
||||||
|
@blur="setUnEditable"
|
||||||
|
/>
|
||||||
<div class="vtl-operation" v-show="isHover">
|
<div class="vtl-operation" v-show="isHover">
|
||||||
<span title="add tree node" @click.stop.prevent="addChild(false)" v-if="!model.isLeaf && !model.addTreeNodeDisabled">
|
<span
|
||||||
<slot name="addTreeNodeIcon">
|
title="add tree node"
|
||||||
|
@click.stop.prevent="addChild(false)"
|
||||||
|
v-if="!model.isLeaf && !model.addTreeNodeDisabled"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
name="addTreeNodeIcon"
|
||||||
|
:expanded="expanded"
|
||||||
|
:model="model"
|
||||||
|
:root="rootNode"
|
||||||
|
>
|
||||||
<i class="vtl-icon vtl-icon-folder-plus-e"></i>
|
<i class="vtl-icon vtl-icon-folder-plus-e"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
<span title="add leaf node" @click.stop.prevent="addChild(true)" v-if="!model.isLeaf && !model.addLeafNodeDisabled">
|
<span
|
||||||
<slot name="addLeafNodeIcon">
|
title="add leaf node"
|
||||||
|
@click.stop.prevent="addChild(true)"
|
||||||
|
v-if="!model.isLeaf && !model.addLeafNodeDisabled"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
name="addLeafNodeIcon"
|
||||||
|
:expanded="expanded"
|
||||||
|
:model="model"
|
||||||
|
:root="rootNode"
|
||||||
|
>
|
||||||
<i class="vtl-icon vtl-icon-plus"></i>
|
<i class="vtl-icon vtl-icon-plus"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
<span title="edit" @click.stop.prevent="setEditable" v-if="!model.editNodeDisabled">
|
<span
|
||||||
<slot name="editNodeIcon">
|
title="edit"
|
||||||
|
@click.stop.prevent="setEditable"
|
||||||
|
v-if="!model.editNodeDisabled"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
name="editNodeIcon"
|
||||||
|
:expanded="expanded"
|
||||||
|
:model="model"
|
||||||
|
:root="rootNode"
|
||||||
|
>
|
||||||
<i class="vtl-icon vtl-icon-edit"></i>
|
<i class="vtl-icon vtl-icon-edit"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
<span title="delete" @click.stop.prevent="delNode" v-if="!model.delNodeDisabled">
|
<span
|
||||||
<slot name="delNodeIcon">
|
title="delete"
|
||||||
|
@click.stop.prevent="delNode"
|
||||||
|
v-if="!model.delNodeDisabled"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
name="delNodeIcon"
|
||||||
|
:expanded="expanded"
|
||||||
|
:model="model"
|
||||||
|
:root="rootNode"
|
||||||
|
>
|
||||||
<i class="vtl-icon vtl-icon-trash"></i>
|
<i class="vtl-icon vtl-icon-trash"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="model.children && model.children.length > 0 && expanded"
|
<div
|
||||||
|
v-if="model.children && model.children.length > 0 && expanded"
|
||||||
class="vtl-border vtl-bottom"
|
class="vtl-border vtl-bottom"
|
||||||
:class="{'vtl-active': isDragEnterBottom}"
|
:class="{'vtl-active': isDragEnterBottom}"
|
||||||
@drop="dropAfter"
|
@drop="dropAfter"
|
||||||
@dragenter="dragEnterBottom"
|
@dragenter="dragEnterBottom"
|
||||||
@dragover='dragOverBottom'
|
@dragover="dragOverBottom"
|
||||||
@dragleave="dragLeaveBottom"></div>
|
@dragleave="dragLeaveBottom"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="{'vtl-tree-margin': model.name !== 'root'}" v-show="model.name === 'root' || expanded" v-if="isFolder">
|
<div
|
||||||
<item v-for="model in model.children"
|
:class="{'vtl-tree-margin': model.name !== 'root'}"
|
||||||
|
v-show="model.name === 'root' || expanded"
|
||||||
|
v-if="isFolder"
|
||||||
|
>
|
||||||
|
<item
|
||||||
|
v-for="model in model.children"
|
||||||
:default-tree-node-name="defaultTreeNodeName"
|
:default-tree-node-name="defaultTreeNodeName"
|
||||||
:default-leaf-node-name="defaultLeafNodeName"
|
:default-leaf-node-name="defaultLeafNodeName"
|
||||||
:default-expanded="defaultExpanded"
|
:default-expanded="defaultExpanded"
|
||||||
:model="model"
|
:model="model"
|
||||||
:key='model.id'>
|
:key="model.id"
|
||||||
<slot name="addTreeNodeIcon" slot="addTreeNodeIcon" />
|
>
|
||||||
<slot name="addLeafNodeIcon" slot="addLeafNodeIcon" />
|
<template v-slot:addTreeNodeIcon="slotProps">
|
||||||
<slot name="editNodeIcon" slot="editNodeIcon" />
|
<slot name="addTreeNodeIcon" v-bind="slotProps" />
|
||||||
<slot name="delNodeIcon" slot="delNodeIcon" />
|
</template>
|
||||||
<slot name="leafNodeIcon" slot="leafNodeIcon" />
|
<template v-slot:addLeafNodeIcon="slotProps">
|
||||||
<slot name="treeNodeIcon" slot="treeNodeIcon" />
|
<slot name="addLeafNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:editNodeIcon="slotProps">
|
||||||
|
<slot name="editNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:delNodeIcon="slotProps">
|
||||||
|
<slot name="delNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:leafNodeIcon="slotProps">
|
||||||
|
<slot name="leafNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:treeNodeIcon="slotProps">
|
||||||
|
<slot name="treeNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
</item>
|
</item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,21 +213,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
rootNode() {
|
||||||
|
var node = this.$parent
|
||||||
|
while (node._props.model.name !== 'root') {
|
||||||
|
node = node.$parent
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
},
|
||||||
|
|
||||||
caretClass() {
|
caretClass() {
|
||||||
return this.expanded ? 'vtl-icon-caret-down' : 'vtl-icon-caret-right'
|
return this.expanded ? 'vtl-icon-caret-down' : 'vtl-icon-caret-right'
|
||||||
},
|
},
|
||||||
|
|
||||||
isFolder() {
|
isFolder() {
|
||||||
return this.model.children &&
|
return this.model.children && this.model.children.length
|
||||||
this.model.children.length
|
|
||||||
},
|
},
|
||||||
|
|
||||||
treeNodeClass() {
|
treeNodeClass() {
|
||||||
const {
|
const {
|
||||||
model: {
|
model: {dragDisabled, disabled},
|
||||||
dragDisabled,
|
|
||||||
disabled
|
|
||||||
},
|
|
||||||
isDragEnterNode
|
isDragEnterNode
|
||||||
} = this
|
} = this
|
||||||
|
|
||||||
@@ -170,15 +260,17 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateName(e) {
|
updateName(e) {
|
||||||
var oldName = this.model.name;
|
var oldName = this.model.name
|
||||||
this.model.changeName(e.target.value)
|
this.model.changeName(e.target.value)
|
||||||
var node = this.getRootNode();
|
this.rootNode.$emit('change-name', {
|
||||||
node.$emit('change-name', {'id': this.model.id, 'oldName': oldName, 'newName': e.target.value})
|
id: this.model.id,
|
||||||
|
oldName: oldName,
|
||||||
|
newName: e.target.value
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
delNode() {
|
delNode() {
|
||||||
var node = this.getRootNode()
|
this.rootNode.$emit('delete-node', this.model)
|
||||||
node.$emit('delete-node', this.model)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setEditable() {
|
setEditable() {
|
||||||
@@ -210,8 +302,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
click() {
|
click() {
|
||||||
var node = this.getRootNode()
|
this.rootNode.$emit('click', this.model)
|
||||||
node.$emit('click', this.model);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addChild(isLeaf) {
|
addChild(isLeaf) {
|
||||||
@@ -219,15 +310,14 @@
|
|||||||
this.expanded = true
|
this.expanded = true
|
||||||
var node = new TreeNode({name, isLeaf})
|
var node = new TreeNode({name, isLeaf})
|
||||||
this.model.addChildren(node, true)
|
this.model.addChildren(node, true)
|
||||||
var root = this.getRootNode();
|
this.rootNode.$emit('add-node', node)
|
||||||
root.$emit('add-node', node)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
dragStart(e) {
|
dragStart(e) {
|
||||||
if (!(this.model.dragDisabled || this.model.disabled)) {
|
if (!(this.model.dragDisabled || this.model.disabled)) {
|
||||||
compInOperation = this
|
compInOperation = this
|
||||||
// for firefox
|
// for firefox
|
||||||
e.dataTransfer.setData("data","data");
|
e.dataTransfer.setData('data', 'data')
|
||||||
e.dataTransfer.effectAllowed = 'move'
|
e.dataTransfer.effectAllowed = 'move'
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -250,11 +340,14 @@
|
|||||||
},
|
},
|
||||||
drop() {
|
drop() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
const oldParent = compInOperation.model.parent;
|
const oldParent = compInOperation.model.parent
|
||||||
compInOperation.model.moveInto(this.model)
|
compInOperation.model.moveInto(this.model)
|
||||||
this.isDragEnterNode = false
|
this.isDragEnterNode = false
|
||||||
var node = this.getRootNode();
|
this.rootNode.$emit('drop', {
|
||||||
node.$emit('drop', {target: this.model, node: compInOperation.model, src: oldParent})
|
target: this.model,
|
||||||
|
node: compInOperation.model,
|
||||||
|
src: oldParent
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
dragEnterUp() {
|
dragEnterUp() {
|
||||||
@@ -271,11 +364,14 @@
|
|||||||
},
|
},
|
||||||
dropBefore() {
|
dropBefore() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
const oldParent = compInOperation.model.parent;
|
const oldParent = compInOperation.model.parent
|
||||||
compInOperation.model.insertBefore(this.model)
|
compInOperation.model.insertBefore(this.model)
|
||||||
this.isDragEnterUp = false
|
this.isDragEnterUp = false
|
||||||
var node = this.getRootNode();
|
this.rootNode.$emit('drop-before', {
|
||||||
node.$emit('drop-before', {target: this.model, node: compInOperation.model, src: oldParent})
|
target: this.model,
|
||||||
|
node: compInOperation.model,
|
||||||
|
src: oldParent
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
dragEnterBottom() {
|
dragEnterBottom() {
|
||||||
@@ -292,18 +388,14 @@
|
|||||||
},
|
},
|
||||||
dropAfter() {
|
dropAfter() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
const oldParent = compInOperation.model.parent;
|
const oldParent = compInOperation.model.parent
|
||||||
compInOperation.model.insertAfter(this.model)
|
compInOperation.model.insertAfter(this.model)
|
||||||
this.isDragEnterBottom = false
|
this.isDragEnterBottom = false
|
||||||
var node = this.getRootNode();
|
this.rootNode.$emit('drop-after', {
|
||||||
node.$emit('drop-after', {target: this.model, node: compInOperation.model, src: oldParent})
|
target: this.model,
|
||||||
},
|
node: compInOperation.model,
|
||||||
getRootNode() {
|
src: oldParent
|
||||||
var node = this.$parent
|
})
|
||||||
while (node._props.model.name !== 'root') {
|
|
||||||
node = node.$parent
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,31 +438,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vtl-icon-file:before {
|
.vtl-icon-file:before {
|
||||||
content: "\e906";
|
content: '\e906';
|
||||||
}
|
}
|
||||||
.vtl-icon-folder:before {
|
.vtl-icon-folder:before {
|
||||||
content: "\e907";
|
content: '\e907';
|
||||||
}
|
}
|
||||||
.vtl-icon-caret-down:before {
|
.vtl-icon-caret-down:before {
|
||||||
content: "\e901";
|
content: '\e901';
|
||||||
}
|
}
|
||||||
.vtl-icon-caret-right:before {
|
.vtl-icon-caret-right:before {
|
||||||
content: "\e900";
|
content: '\e900';
|
||||||
}
|
}
|
||||||
.vtl-icon-edit:before {
|
.vtl-icon-edit:before {
|
||||||
content: "\e902";
|
content: '\e902';
|
||||||
}
|
}
|
||||||
.vtl-icon-folder-plus-e:before {
|
.vtl-icon-folder-plus-e:before {
|
||||||
content: "\e903";
|
content: '\e903';
|
||||||
}
|
}
|
||||||
.vtl-icon-plus:before {
|
.vtl-icon-plus:before {
|
||||||
content: "\e904";
|
content: '\e904';
|
||||||
}
|
}
|
||||||
.vtl-icon-trash:before {
|
.vtl-icon-trash:before {
|
||||||
content: "\e905";
|
content: '\e905';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.vtl-border {
|
.vtl-border {
|
||||||
height: 5px;
|
height: 5px;
|
||||||
&.vtl-up {
|
&.vtl-up {
|
||||||
@@ -410,7 +501,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.vtl-item {
|
.vtl-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
26
src/tools.js
26
src/tools.js
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
var handlerCache
|
var handlerCache
|
||||||
|
|
||||||
exports.addHandler = function (element, type, handler) {
|
export const addHandler = function (element, type, handler) {
|
||||||
handlerCache = handler
|
handlerCache = handler
|
||||||
if (element.addEventListener) {
|
if (element.addEventListener) {
|
||||||
element.addEventListener(type, handler, false)
|
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) {
|
if (element.removeEventListener) {
|
||||||
element.removeEventListener(type, handlerCache, false)
|
element.removeEventListener(type, handlerCache, false)
|
||||||
} else if (element.detachEvent) {
|
} else if (element.detachEvent) {
|
||||||
@@ -25,7 +25,21 @@ exports.removeHandler = function (element, type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// exports.fireFocusEvent = function (ele) {
|
// depth first search
|
||||||
// var event = new FocusEvent()
|
export const traverseTree = (root) => {
|
||||||
// ele.dispatch(event)
|
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
|
||||||
|
}
|
||||||
|
53
tests/unit/__snapshots__/slot.spec.js.snap
Normal file
53
tests/unit/__snapshots__/slot.spec.js.snap
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Slot render slot correctly 1`] = `
|
||||||
|
<div class="vtl">
|
||||||
|
<!---->
|
||||||
|
<div class="">
|
||||||
|
<div class="vtl">
|
||||||
|
<div id="t1" class="vtl-node vtl-tree-node">
|
||||||
|
<div class="vtl-border vtl-up"></div>
|
||||||
|
<div draggable="true" class="vtl-node-main"><span class="vtl-caret vtl-is-small"><i class="vtl-icon vtl-icon-caret-down"></i></span> <span><span class="tree-node-icon icon">❀</span></span>
|
||||||
|
<div class="vtl-node-content">
|
||||||
|
Node 1
|
||||||
|
</div>
|
||||||
|
<div class="vtl-operation" style="display: none;"><span title="add tree node"><span class="add-tree-node-icon">📂</span></span> <span title="add leaf node"><span class="icon">+</span></span> <span title="edit"><span class="icon">📃</span></span> <span title="delete"><span></span></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="vtl-border vtl-bottom"></div>
|
||||||
|
</div>
|
||||||
|
<div class="vtl-tree-margin">
|
||||||
|
<div class="vtl">
|
||||||
|
<div id="t11" class="vtl-node vtl-leaf-node">
|
||||||
|
<div class="vtl-border vtl-up"></div>
|
||||||
|
<div draggable="true" class="vtl-node-main">
|
||||||
|
<!----> <span><span class="icon">🍃</span></span>
|
||||||
|
<div class="vtl-node-content">
|
||||||
|
Node 1-1
|
||||||
|
</div>
|
||||||
|
<div class="vtl-operation" style="display: none;">
|
||||||
|
<!---->
|
||||||
|
<!----> <span title="edit"><span class="icon">📃</span></span> <span title="delete"><span class="del-node-icon">✂️</span></span></div>
|
||||||
|
</div>
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="vtl">
|
||||||
|
<div id="t2" class="vtl-node vtl-tree-node">
|
||||||
|
<div class="vtl-border vtl-up"></div>
|
||||||
|
<div draggable="true" class="vtl-node-main">
|
||||||
|
<!----> <span><span class="tree-node-icon icon">❀</span></span>
|
||||||
|
<div class="vtl-node-content">
|
||||||
|
Node 2
|
||||||
|
</div>
|
||||||
|
<div class="vtl-operation" style="display: none;"><span title="add tree node"><span class="add-tree-node-icon">📂</span></span> <span title="add leaf node"><span class="icon">+</span></span> <span title="edit"><span class="icon">📃</span></span> <span title="delete"><span class="del-node-icon">✂️</span></span></div>
|
||||||
|
</div>
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
79
tests/unit/slot.spec.js
Normal file
79
tests/unit/slot.spec.js
Normal file
@@ -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 <span class="add-tree-node-icon">📂</span>
|
||||||
|
},
|
||||||
|
addLeafNodeIcon() {
|
||||||
|
return <span class="icon">+</span>
|
||||||
|
},
|
||||||
|
editNodeIcon() {
|
||||||
|
return <span class="icon">📃</span>
|
||||||
|
},
|
||||||
|
delNodeIcon(slotProps) {
|
||||||
|
return (slotProps.model.isLeaf || !slotProps.model.children) ? <span class="del-node-icon">✂️</span> : <span />
|
||||||
|
},
|
||||||
|
leafNodeIcon() {
|
||||||
|
return <span class="icon">🍃</span>
|
||||||
|
},
|
||||||
|
treeNodeIcon(slotProps) {
|
||||||
|
return <span class="tree-node-icon icon">{ slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded ? '🌲' : '❀' }</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
5
vue.config.js
Normal file
5
vue.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
css: {
|
||||||
|
extract: false
|
||||||
|
}
|
||||||
|
};
|
Reference in New Issue
Block a user