Compare commits

...

102 Commits

Author SHA1 Message Date
youxingzhi
5500a36492 1.4.6 2020-07-02 09:59:35 +08:00
ayou
66bc19baaa Merge pull request #78 from zoispag/feat/74_slot_for_display_name
Feat: Add slot for display node/leaf name 
2020-07-02 09:57:17 +08:00
Zois Pagoulatos
97daf67f9a Add slot for display node/leaf name (#74) 2020-07-01 11:26:10 +02:00
youxingzhi
d1e12aad85 1.4.5 2020-05-30 22:13:14 +08:00
youxingzhi
d5dd9b88c9 fix: 🐛 #72 2020-05-30 22:12:00 +08:00
youxingzhi
ccb1067713 1.4.4 2020-04-25 19:44:54 +08:00
ayou
d5705f92e8 Merge pull request #68 from laashub/master
update: after blur emit the event for changeName
2020-04-10 11:48:34 +08:00
tristan-tsl
ae494ce25f update: after blur emit the event for changeName 2020-04-06 13:11:15 +08:00
tristan-tsl
c59015fdf7 update: after blur emit the event for changeName 2020-04-06 13:05:32 +08:00
ayou
5f94ebf51b Merge pull request #65 from ParadeTo/feature-code-format
chore: 🤖 add code format tool
2020-01-30 13:36:42 +08:00
youxingzhi
53320cc235 chore: 🤖 change lint-staged conf 2020-01-30 13:34:24 +08:00
youxingzhi
d73b4c1829 chore: 🤖 add code format tool 2020-01-30 12:01:36 +08:00
ayou
15f33d187d Merge pull request #64 from ParadeTo/fix-#62
fix: 🐛 #62
2020-01-30 10:44:55 +08:00
youxingzhi
a67e39ce31 fix: 🐛 #62 2020-01-30 10:43:51 +08:00
youxingzhi
69ffc1da0f 1.4.3 2020-01-07 11:44:57 +08:00
ayou
31c9225441 Merge pull request #56 from ParadeTo/bugfix/style-including
bugfix: extracted css file
2020-01-07 11:44:07 +08:00
ayou
64c56af961 Merge pull request #57 from ParadeTo/feature/plugin-export
feat: plugin export
2020-01-07 11:43:46 +08:00
Binbin Sun
a792ee3910 chore: 🤖 move core-js & vue to devDependencies 2020-01-07 11:38:23 +08:00
Binbin Sun
aa3359155f docs: ✏️ install doc 2020-01-07 11:27:16 +08:00
Binbin Sun
c1270b880b feat: 🎸 export default with the install method 2020-01-07 11:21:17 +08:00
Binbin Sun
d8a5da1e0e feat: 🎸 extracted css file
Closes: #55
2020-01-07 10:59:06 +08:00
youxingzhi
780d42c6ea 1.4.2 2020-01-06 17:40:23 +08:00
ayou
9c2d25e313 Merge pull request #54 from ParadeTo/feature-#47
feat: 🎸 #47
2020-01-06 17:37:43 +08:00
youxingzhi
fbd370e9e5 feat: 🎸 #47 2020-01-03 23:19:55 +08:00
ayou
332402dee6 Merge pull request #53 from weiqian93/master
feat: support different icon for opened node
2020-01-03 21:33:58 +08:00
qian.wei
61ae848898 feat: 🎸 support different icon for opened node
Closes: #47
2020-01-03 19:44:42 +08:00
ayou
82e87e493a Merge pull request #51 from ParadeTo/feature-add-tests
Feature add tests
2019-12-31 09:18:31 +08:00
ayou
2427f47201 Merge pull request #52 from ParadeTo/add-license-1
Create LICENSE
2019-12-30 22:48:45 +08:00
ayou
fab494cedf Create LICENSE 2019-12-30 22:46:31 +08:00
youxingzhi
4e37591f10 test: 💍 update snapshot 2019-12-30 22:45:07 +08:00
youxingzhi
8b23327cee ci: 🎡 change test:unit to test:coverage 2019-12-30 22:39:13 +08:00
youxingzhi
ad6ad1b255 1.4.1 2019-12-30 21:51:20 +08:00
youxingzhi
2fe4da8605 docs: ✏️ add action badge 2019-12-30 21:48:06 +08:00
youxingzhi
d545e6bdbb Merge remote-tracking branch 'origin/master' into feature-add-tests 2019-12-30 21:35:46 +08:00
youxingzhi
d4f911b7b5 test: 💍 add some tests 2019-12-30 12:17:51 +08:00
ayou
9ded7fe914 Merge pull request #50 from ParadeTo/feature/github-actions
Create test.yml
2019-12-29 22:45:39 +08:00
Binbin Sun
b07e14b9ff chore: 🤖 update test.yml 2019-12-29 22:40:08 +08:00
Binbin Sun
fa2b7cb9dd Create test.yml 2019-12-29 22:31:23 +08:00
youxingzhi
57be520b99 1.4.1 2019-12-29 21:25:47 +08:00
ayou
7b70e8dbf2 Merge pull request #49 from beyoursun/master
Add unit test
2019-12-29 21:01:36 +08:00
Binbin Sun
b9402076ec chore: 🤖 change lock registry to official npm 2019-12-29 20:46:21 +08:00
Binbin Sun
669e84fdc5 chore: 🤖 add snapshot testing 2019-12-29 11:23:46 +08:00
Binbin Sun
146b61e70d chore: 🤖 add unit test 2019-12-29 11:14:03 +08:00
ayou
b3e238208b Merge pull request #48 from beyoursun/master
Migrate to vue cli 3
2019-12-27 14:26:25 +08:00
Binbin Sun
4b8bbcbfde chore: 🤖 migrate to vue cli 3 2019-12-27 14:14:34 +08:00
youxingzhi
0723294c7b 1.4.0 2019-12-08 17:41:49 +08:00
youxingzhi
22988f0c81 refactor: 💡 rename the slot name 2019-12-08 17:41:41 +08:00
ayou
c46c570c30 Merge pull request #46 from Lwtsde/slot-fix
fix(slots): fixed slots in childrens
2019-12-08 17:22:13 +08:00
Lucas Wassenhoven
cfe90c7f81 fix(slots): fixed slots in childrens 2019-12-06 11:50:33 +01:00
ayou
7e0184f7f5 Merge pull request #42 from evillt-bot/master
chore(readme): format code
2019-08-30 22:07:09 +08:00
EVILLT
a78ff3f904 chore(readme): update 2019-08-30 16:56:01 +08:00
youxingzhi
cf41282fcf feat(event): add drop-before and drop-after 2019-07-17 09:54:15 +08:00
ayou
6154cfd1ad Merge pull request #37 from zymawy/patch-1
Duplicate Declaration On onClick Method
2019-06-16 10:47:35 +08:00
Hamza Ironside
51167862c9 Duplicate Declaration On onClick Method
There Were Two Methods With The Same Name
2019-06-15 05:56:21 +03:00
ayou
e38102ed71 Merge pull request #27 from iakta/feature/drop-events
Feature/drop events
2019-03-15 22:49:45 +08:00
Salvatore Giordano
d605642199 feat(events): add docs for drop and drop-up 2019-03-13 09:48:20 +01:00
Salvatore Giordano
62335d9eac feat(events): rename event using dashes 2019-03-13 09:45:25 +01:00
Salvatore Giordano
625dadcb44 feat(events): add dropBottom event 2019-03-13 09:30:13 +01:00
youxingzhi
87b8a30bfb fix(demo): change param name 2019-03-13 12:09:53 +08:00
youxingzhi
2164d032ca feat: #23 2019-03-13 12:05:34 +08:00
Salvatore Giordano
538678d916 fix(events): fix oldParent object in drop events 2019-03-12 16:29:49 +01:00
Salvatore Giordano
dd3d61ddf7 feat(events): update version 2019-03-12 16:21:58 +01:00
Salvatore Giordano
e7757b97a7 feat(events): add oldParent information in drop event 2019-03-12 16:15:18 +01:00
Salvatore Giordano
76e67c583c feat(events): emit drop and dropup events 2019-03-12 15:20:52 +01:00
youxingzhi
106708217f chore(readme): fix format 2019-02-23 11:58:16 +08:00
youxingzhi
165d2dfb5d chore(version): 1.3.0 2019-02-23 11:54:26 +08:00
youxingzhi
8f0a5d7737 feat: support closing operation icons 2019-02-23 11:52:58 +08:00
youxingzhi
a3b03a9579 chore(version): 1.2.0 2019-02-23 10:46:47 +08:00
youxingzhi
136b33971c feat: support customizing operation icons 2019-02-23 10:44:34 +08:00
youxingzhi
3dd6e31028 chore(version): 1.1.2 2019-02-15 09:43:24 +08:00
youxingzhi
cde7836539 refactor: change event name 2019-02-15 09:41:53 +08:00
ayou
a8284583da Merge pull request #20 from energiehund/feature/events-for-node-operations
feat: Emit events for node operations
2019-02-15 09:23:25 +08:00
energiehund
8cb42cd952 feat: Emit events for node operations 2019-02-13 17:14:53 +08:00
youxingzhi
5ab8775ace chore: 1.1.1 2019-01-03 09:56:25 +08:00
youxingzhi
3fec41a890 fix: add semi 2019-01-03 09:55:27 +08:00
youxingzhi
867ef2a7fa chore(add husky): add standard and commitlint 2018-12-11 11:16:29 +08:00
youxingzhi
2b2b1419ad chore: update demo
update demo because of new style
2018-12-11 11:14:49 +08:00
ayou
47a4e2df8d Merge pull request #16 from ParadeTo/fix-issue#14
fix issue #14, make delete node configurable
2018-12-11 10:29:18 +08:00
ayou
ec49d0e226 Merge branch 'master' into fix-issue#14 2018-12-11 10:29:07 +08:00
ayou
52a63af52f Merge pull request #15 from ParadeTo/fix-issue#11
Fix issue#11
2018-12-11 10:23:41 +08:00
ayou
b10803df64 Merge pull request #17 from ParadeTo/fix-issue#13
fix issue #13
2018-12-08 22:20:50 +08:00
Zhiyuan Guo
7d35504b40 rm logs 2018-12-08 15:13:08 +08:00
Zhiyuan Guo
b2eba3e3c9 update readme 2018-12-08 15:11:11 +08:00
Zhiyuan Guo
d6826e1a24 fix issue #14, make delete node configurable 2018-12-08 15:08:43 +08:00
Zhiyuan Guo
a815fc658b fix issue #11 2018-12-08 14:43:35 +08:00
Zhiyuan Guo
572b045314 fix issue #13 2018-12-08 11:53:36 +08:00
youxingzhi
7bc8a7a17f 1.1.0 2018-11-28 11:31:36 +08:00
youxingzhi
c3019872c5 refactor style 2018-11-28 11:28:04 +08:00
youxingzhi
162c911f3b fix arrow's style bug 2018-11-28 10:31:16 +08:00
youxingzhi
a7b717334c 1.0.8 2018-03-09 17:22:33 +08:00
youxingzhi
f6de5a2be8 1.0.7 2018-03-09 17:20:09 +08:00
ayou
2367a7d1e6 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	readme.md
2018-03-07 22:03:26 +08:00
ayou
01eca7d2a3 add dev mode, update readme 2018-03-07 22:03:07 +08:00
ayou
2f1205ae02 Update readme.md 2018-03-07 21:39:46 +08:00
ayou
fc5b9f58ff 1.0.5 2018-02-11 21:37:56 +08:00
ayou
c846f7a9c1 add node click event 2018-02-11 21:35:53 +08:00
ayou
1580a3524d 1.0.4 2018-02-06 21:58:05 +08:00
ayou
6812b1eb30 remove jquery 2018-02-06 21:51:45 +08:00
youxingzhi
bbbe58a4e0 1.0.3 2018-01-05 10:59:54 +08:00
youxingzhi
97f0db4343 #3 add dragDisabled params 2018-01-05 10:52:09 +08:00
youxingzhi
527bae5bdc 1.0.2 2017-08-31 18:27:39 +08:00
youxingzhi
dce8f74717 #1 2017-08-31 18:25:47 +08:00
32 changed files with 16797 additions and 654 deletions

View File

@@ -1,10 +0,0 @@
{
"presets": ["es2015", "stage-2"],
"plugins": ["transform-runtime"],
"comments": false,
"env": {
"test": {
"plugins": [ "istanbul" ]
}
}
}

2
.commitlintrc.yml Normal file
View File

@@ -0,0 +1,2 @@
extends:
- "@commitlint/config-conventional"

3
.czrc Normal file
View File

@@ -0,0 +1,3 @@
{
"path": "cz-conventional-changelog"
}

29
.eslintrc Normal file
View File

@@ -0,0 +1,29 @@
{
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"plugin:prettier/recommended",
"eslint:recommended"
],
"rules": {
"prettier/prettier": "error",
"no-console": "warn"
},
"parserOptions": {
"parser": "babel-eslint"
},
"overrides": [
{
"files": [
"**/__tests__/*.{j,t}s?(x)",
"**/tests/unit/**/*.spec.{j,t}s?(x)"
],
"env": {
"jest": true
}
}
]
}

15
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Test
on: [pull_request, push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: npm install, lint and test
run: |
npm install
npm run lint
npm run test:coverage

23
.gitignore vendored
View File

@@ -1,4 +1,23 @@
.DS_Store
node_modules
npm-debug.log
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
coverage
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
coverage

3
.huskyrc.yml Normal file
View File

@@ -0,0 +1,3 @@
hooks:
pre-commit: npm run lint-staged
commit-msg: commitlint -E HUSKY_GIT_PARAMS

View File

@@ -9,4 +9,4 @@ test
.babelrc
.travis.yml
karma.conf.js
webpack.config.js
build

21
LICENSE Normal file
View 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.

3
babel.config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset']
}

188
dev/App.vue Normal file
View File

@@ -0,0 +1,188 @@
<template>
<div>
<button @click="addNode">Add Node</button>
<vue-tree-list
@click="onClick"
@change-name="onChangeName"
@delete-node="onDel"
@add-node="onAddNode"
@drop="drop"
@drop-before="dropBefore"
@drop-after="dropAfter"
:model="data"
default-tree-node-name="new node"
default-leaf-node-name="new leaf"
v-bind:default-expanded="false"
>
<template v-slot:leafNameDisplay="slotProps">
<span>
{{ slotProps.model.name }} <span class="muted">#{{ slotProps.model.id }}</span>
</span>
</template>
<template v-slot:addTreeNodeIcon="slotProps">
<span class="icon">📂</span>
</template>
<template v-slot:addLeafNodeIcon="slotProps">
<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>
<button @click="getNewTree">Get new tree</button>
<pre>
{{ newTree }}
</pre>
</div>
</template>
<script>
import { VueTreeList, Tree, TreeNode } from '../src'
export default {
components: {
VueTreeList
},
data() {
return {
newTree: {},
data: new Tree([
{
name: 'Node 1',
id: 1,
pid: 0,
dragDisabled: true,
addTreeNodeDisabled: true,
addLeafNodeDisabled: true,
editNodeDisabled: true,
delNodeDisabled: true,
children: [
{
name: 'Node 1-2',
id: 2,
isLeaf: true,
pid: 1
}
]
},
{
name: 'Node 2',
id: 3,
pid: 0,
disabled: true
},
{
name: 'Node 3',
id: 4,
pid: 0
}
])
}
},
methods: {
onDel(node) {
// eslint-disable-next-line no-console
console.log(node)
node.remove()
},
onChangeName(params) {
// eslint-disable-next-line no-console
console.log(params)
},
onAddNode(params) {
// eslint-disable-next-line no-console
console.log(params)
},
onClick(params) {
// eslint-disable-next-line no-console
console.log(params)
},
drop: function({ node, src, target }) {
// eslint-disable-next-line no-console
console.log('drop', node, src, target)
},
dropBefore: function({ node, src, target }) {
// eslint-disable-next-line no-console
console.log('drop-before', node, src, target)
},
dropAfter: function({ node, src, target }) {
// eslint-disable-next-line no-console
console.log('drop-after', node, src, target)
},
addNode() {
var node = new TreeNode({ name: 'new node', isLeaf: false })
if (!this.data.children) this.data.children = []
this.data.addChildren(node)
},
getNewTree() {
var vm = this
function _dfs(oldNode) {
var newNode = {}
for (var k in oldNode) {
if (k !== 'children' && k !== 'parent') {
newNode[k] = oldNode[k]
}
}
if (oldNode.children && oldNode.children.length > 0) {
newNode.children = []
for (var i = 0, len = oldNode.children.length; i < len; i++) {
newNode.children.push(_dfs(oldNode.children[i]))
}
}
return newNode
}
vm.newTree = _dfs(vm.data)
}
}
}
</script>
<style lang="less" rel="stylesheet/less">
.vtl {
.vtl-drag-disabled {
background-color: #d0cfcf;
&:hover {
background-color: #d0cfcf;
}
}
.vtl-disabled {
background-color: #d0cfcf;
}
}
</style>
<style lang="less" rel="stylesheet/less" scoped>
.icon {
&:hover {
cursor: pointer;
}
}
.muted {
color: gray;
font-size: 80%;
}
</style>

9
dev/index.js Normal file
View File

@@ -0,0 +1,9 @@
/**
* Created by ayou on 18/3/7.
*/
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App)
}).$mount('#app')

File diff suppressed because one or more lines are too long

View File

@@ -59,6 +59,27 @@
}
}
.vtl .vtl-drag-disabled, .vtl .vtl-drag-disabled:hover {
background-color: #dbd7d7;
position: relative;
}
.vtl .vtl-drag-disabled::after, .vtl .vtl-disabled::after {
content: 'drag disabled';
position: absolute;
right: 10px;
font-size: 12px;
font-style: italic;
}
.vtl .vtl-disabled, .vtl .vtl-disabled:hover {
background-color: red;
position: relative;
}
.vtl .vtl-disabled::after {
content: 'disabled';
}
</style>
</head>
<body>
@@ -78,7 +99,7 @@
</span>
New Treenode
</button>
<vue-tree-list :model="data" default-tree-node-name="new node" default-leaf-node-name="new leaf"></vue-tree-list>
<vue-tree-list @click="onClick" :model="data" default-tree-node-name="new node" default-leaf-node-name="new leaf"></vue-tree-list>
</div>
<div class="container">
@@ -127,6 +148,7 @@
name: 'Node 1',
id: 1,
pid: 0,
dragDisabled: true,
children: [
{
name: 'Node 1-2',
@@ -139,7 +161,8 @@
{
name: 'Node 2',
id: 3,
pid: 0
pid: 0,
disabled: true
},
{
name: 'Node 3',
@@ -155,24 +178,25 @@
},
addNode: function () {
var node = new VueTreeList.TreeNode('new node', false)
var node = new VueTreeList.TreeNode({ name: 'new node', isLeaf: false })
if (!this.data.children) this.data.children = []
this.data.addChildren(node)
},
getNewTree: function () {
const vm = this
var vm = this
function _dfs (oldNode) {
let newNode = {}
var newNode = {}
newNode.name = oldNode.name
newNode.pid = oldNode.pid
newNode.isLeaf = oldNode.isLeaf
newNode.id = oldNode.id
for (var k in oldNode) {
if (k !== 'children' && k !== 'parent') {
newNode[k] = oldNode[k]
}
}
if (oldNode.children && oldNode.children.length > 0) {
newNode.children = []
for (let i = 0, len = oldNode.children.length; i < len; i++) {
for (var i = 0, len = oldNode.children.length; i < len; i++) {
newNode.children.push(_dfs(oldNode.children[i]))
}
}
@@ -180,6 +204,10 @@
}
vm.newTree = _dfs(vm.data)
},
onClick(model) {
console.log(model)
}
}
})

6
jest.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
preset: '@vue/cli-plugin-unit-jest',
snapshotSerializers: ['jest-serializer-vue'],
collectCoverageFrom: ['src/**/*.{js,vue}'],
coveragePathIgnorePatterns: ['src/index.js']
}

15112
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +1,62 @@
{
"name": "vue-tree-list",
"version": "1.0.1",
"version": "1.4.6",
"description": "A vue component for tree structure. Support adding treenode/leafnode, editing node's name and dragging.",
"main": "dist/vue-tree-list.min.js",
"scripts": {
"test": "karma start --single-run",
"unit": "karma start --watch",
"coveralls": "npm run test -- --report lcovonly && cat ./coverage/lcov.info | coveralls",
"build": "webpack"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ParadeTo/vue-tree-list.git"
},
"keywords": [
"vue",
"tree"
],
"author": "ayou",
"license": "ISC",
"scripts": {
"serve": "vue-cli-service serve dev",
"build": "vue-cli-service build --target lib src/index.js",
"test:unit": "vue-cli-service test:unit --watch",
"test:coverage": "vue-cli-service test:unit --coverage",
"lint": "vue-cli-service lint",
"lint-staged": "lint-staged",
"commit": "npx git-cz",
"prepublish": "npm run build"
},
"main": "dist/vue-tree-list.umd.min.js",
"dependencies": {},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-plugin-unit-jest": "^4.1.1",
"@vue/cli-service": "^4.1.0",
"@vue/test-utils": "1.0.0-beta.29",
"babel-eslint": "^10.0.3",
"core-js": "^3.4.3",
"eslint": "^5.16.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-vue": "^5.0.0",
"husky": "^4.2.1",
"jest-serializer-vue": "^2.0.2",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"lint-staged": "^10.0.4",
"prettier": "^1.19.1",
"prettier-eslint-cli": "^5.0.0",
"vue": "^2.6.10",
"vue-template-compiler": "^2.6.10"
},
"lint-staged": {
"**/*.{js,json,md,vue}": [
"prettier --write"
]
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"bugs": {
"url": "https://github.com/ParadeTo/vue-tree-list/issues"
},
"homepage": "https://github.com/ParadeTo/vue-tree-list#readme",
"dependencies": {
"jquery": "^3.2.1"
},
"devDependencies": {
"autoprefixer": "^6.4.0",
"babel-core": "^6.0.0",
"babel-eslint": "^7.0.0",
"babel-loader": "^6.0.0",
"babel-plugin-istanbul": "^3.0.0",
"babel-plugin-transform-runtime": "^6.0.0",
"babel-preset-es2015": "^6.0.0",
"babel-preset-stage-2": "^6.0.0",
"babel-register": "^6.0.0",
"chai": "^3.5.0",
"cross-env": "^5.0.1",
"css-loader": "^0.26.2",
"file-loader": "^0.10.1",
"isparta": "^4.0.0",
"isparta-loader": "^2.0.0",
"karma": "^1.7.0",
"karma-coverage": "^1.1.1",
"karma-coveralls": "^1.1.2",
"karma-mocha": "^1.3.0",
"karma-phantomjs-launcher": "^1.0.4",
"karma-sinon-chai": "^1.3.1",
"karma-webpack": "^2.0.3",
"less": "^2.7.2",
"less-loader": "^2.2.3",
"mocha": "^3.4.2",
"phantomjs-prebuilt": "^2.1.14",
"sinon": "^2.3.5",
"sinon-chai": "^2.11.0",
"sourcemap": "^0.1.0",
"url-loader": "^0.5.7",
"vue": "^2.3.4",
"vue-loader": "^11.1.3",
"vue-style-loader": "^2.0.3",
"vue-template-compiler": "^2.2.0",
"webpack": "^1.13.2"
"keywords": [
"vue",
"tree"
],
"license": "ISC",
"repository": {
"type": "git",
"url": "git+https://github.com/ParadeTo/vue-tree-list.git"
}
}

15
prettier.config.js Normal file
View File

@@ -0,0 +1,15 @@
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
jsxSingleQuote: true,
bracketSpacing: true,
jsxBracketSameLine: false,
rangeStart: 0,
rangeEnd: Infinity,
requirePragma: false,
insertPragma: false,
htmlWhitespaceSensitivity: 'css'
}

17
public/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>vue-tree-list</title>
</head>
<body>
<noscript>
<strong>We're sorry but vue-tree-list doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

227
readme.md
View File

@@ -1,27 +1,70 @@
[![Actions Status](https://github.com/ParadeTo/vue-tree-list/workflows/Test/badge.svg)](https://github.com/ParadeTo/vue-tree-list/actions)
# vue-tree-list
A vue component for tree structure. Support adding treenode/leafnode, editing node's name and dragging.
![vue-tree-demo.gif](https://raw.githubusercontent.com/ParadeTo/vue-tree-list/master/img/demo.gif)
[Live Demo](http://paradeto.com/vue-tree-list/)
# use
``npm install vue-tree-list``
# install
```javascript
<button @click="addNode">Add Node</button>
<vue-tree-list :model="data" default-tree-node-name="new node" default-leaf-node-name="new leaf"></vue-tree-list>
<button @click="getNewTree">Get new tree</button>
<pre>
{{newTree}}
</pre>
...
import { VueTreeList, Tree, TreeNode } from 'vue-tree-list'
export default {
Install the plugin then you can use the component globally.
```js
import Vue from 'vue'
import VueTreeList from 'vue-tree-list'
Vue.use(VueTreeList)
```
Or just register locally like the example below.
# use
`npm install vue-tree-list`
```html
<template>
<div>
<button @click="addNode">Add Node</button>
<vue-tree-list
@click="onClick"
@change-name="onChangeName"
@delete-node="onDel"
@add-node="onAddNode"
:model="data"
default-tree-node-name="new node"
default-leaf-node-name="new leaf"
v-bind:default-expanded="false"
>
<template v-slot:leafNameDisplay="slotProps">
<span>
{{ slotProps.model.name }} <span class="muted">#{{ slotProps.model.id }}</span>
</span>
</template>
<span class="icon" slot="addTreeNodeIcon">📂</span>
<span class="icon" slot="addLeafNodeIcon"></span>
<span class="icon" slot="editNodeIcon">📃</span>
<span class="icon" slot="delNodeIcon">✂️</span>
<span class="icon" slot="leafNodeIcon">🍃</span>
<span class="icon" slot="treeNodeIcon">🌲</span>
</vue-tree-list>
<button @click="getNewTree">Get new tree</button>
<pre>
{{newTree}}
</pre>
</div>
</template>
<script>
import { VueTreeList, Tree, TreeNode } from 'vue-tree-list'
export default {
components: {
VueTreeList
},
data () {
data() {
return {
newTree: {},
data: new Tree([
@@ -29,6 +72,11 @@ export default {
name: 'Node 1',
id: 1,
pid: 0,
dragDisabled: true,
addTreeNodeDisabled: true,
addLeafNodeDisabled: true,
editNodeDisabled: true,
delNodeDisabled: true,
children: [
{
name: 'Node 1-2',
@@ -41,7 +89,8 @@ export default {
{
name: 'Node 2',
id: 3,
pid: 0
pid: 0,
disabled: true
},
{
name: 'Node 3',
@@ -52,25 +101,43 @@ export default {
}
},
methods: {
addNode: function () {
var node = new TreeNode('new node', false)
onDel(node) {
console.log(node)
node.remove()
},
onChangeName(params) {
console.log(params)
},
onAddNode(params) {
console.log(params)
},
onClick(params) {
console.log(params)
},
addNode() {
var node = new TreeNode({ name: 'new node', isLeaf: false })
if (!this.data.children) this.data.children = []
this.data.addChildren(node)
},
getNewTree: function () {
const vm = this
function _dfs (oldNode) {
let newNode = {}
getNewTree() {
var vm = this
function _dfs(oldNode) {
var newNode = {}
newNode.name = oldNode.name
newNode.pid = oldNode.pid
newNode.isLeaf = oldNode.isLeaf
newNode.id = oldNode.id
for (var k in oldNode) {
if (k !== 'children' && k !== 'parent') {
newNode[k] = oldNode[k]
}
}
if (oldNode.children && oldNode.children.length > 0) {
newNode.children = []
for (let i = 0, len = oldNode.children.length; i < len; i++) {
for (var i = 0, len = oldNode.children.length; i < len; i++) {
newNode.children.push(_dfs(oldNode.children[i]))
}
}
@@ -80,9 +147,113 @@ export default {
vm.newTree = _dfs(vm.data)
}
}
}
}
</script>
<style lang="less" rel="stylesheet/less">
.vtl {
.vtl-drag-disabled {
background-color: #d0cfcf;
&:hover {
background-color: #d0cfcf;
}
}
.vtl-disabled {
background-color: #d0cfcf;
}
}
</style>
<style lang="less" rel="stylesheet/less" scoped>
.icon {
&:hover {
cursor: pointer;
}
}
.muted {
color: gray;
font-size: 80%;
}
</style>
```
# props
default-tree-node-name: Default name for new treenode.
default-leaf-node-name: Default name for new leafnode.
## props of vue-tree-list
| name | type | default | description |
| :--------------------: | :------: | :-----------: | :-----------------------------------------------------------------------------------------: |
| model | TreeNode | - | You can use `const head = new Tree([])` to generate a tree with the head of `TreeNode` type |
| default-tree-node-name | string | New node node | Default name for new treenode |
| default-leaf-node-name | string | New leaf node | Default name for new leafnode |
| default-expanded | boolean | true | Tree is expanded or not |
## props of TreeNode
### attributes
| name | type | default | description |
| :-----------------: | :------------: | :---------------: | :------------------------------: |
| id | string, number | current timestamp | The node's id |
| isLeaf | boolean | false | The node is leaf or not |
| dragDisabled | boolean | false | Forbid dragging tree node |
| addTreeNodeDisabled | boolean | false | Show `addTreeNode` button or not |
| addLeafNodeDisabled | boolean | false | Show `addLeafNode` button or not |
| editNodeDisabled | boolean | false | Show `editNode` button or not |
| delNodeDisabled | boolean | false | Show `delNode` button or not |
| children | array | null | The children of node |
### methods
| name | params | description |
| :----------: | :---------------------: | :---------------------------: |
| changeName | name | Change node's name |
| addChildren | children: object, array | Add children to node |
| remove | - | Remove node from the tree |
| moveInto | target: TreeNode | Move node into another node |
| insertBefore | target: TreeNode | Move node before another node |
| insertAfter | target: TreeNode | Move node after another node |
# events
| name | params | description |
| :---------: | :--------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| click | TreeNode | Trigger when clicking a tree node |
| change-name | {'id', 'oldName', 'newName'} | Trigger after changing a node's name |
| delete-node | TreeNode | Trigger when clicking `delNode` button. You can call `remove` of `TreeNode` to remove the node. |
| add-node | TreeNode | Trigger after adding a new node |
| drop | {node, src, target} | Trigger after dropping a node into another. node: the draggable node, src: the draggable node's parent, target: the node that draggable node will drop into |
| drop-before | {node, src, target} | Trigger after dropping a node before another. node: the draggable node, src: the draggable node's parent, target: the node that draggable node will drop before |
| 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 and can access `model`, `root`, `expanded` as below:
```html
<template v-slot:leafNameDisplay="slotProps">
<span>{{ slotProps.model.name }} #{{ slotProps.model.id }}</span>
</template>
<template v-slot:addTreeNodeIcon="slotProps">
<span class="icon">📂</span>
</template>
<template v-slot:addLeafNodeIcon="slotProps">
<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>
```

View File

@@ -1,201 +1,168 @@
// used to record treenode's change
let Record = {}
// treenode's id
let nodeId = 1
import { traverseTree } from './tools'
/**
* Tree data struct
* Created by ayou on 2017/7/20.
* @param name: treenode's name
* @param isLeaf: treenode is leaf node or not
* @param id
* @param data: treenode's params
* name: treenode's name
* isLeaf: treenode is leaf node or not
* id: id
* dragDisabled: decide if it can be dragged
* disabled: desabled all operation
*/
const TreeNode = function (name, isLeaf, id) {
this.name = name
this.id = (typeof id === 'undefined') ? ('new' + nodeId++) : id
this.parent = null
this.pid = 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
TreeNode.prototype.updateRecordProperty = function () {
if (!Record[this.id]) {
Record[this.id] = {}
}
Record[this.id].name = this.name
Record[this.id].id = this.id
Record[this.id].pid = this.pid
Record[this.id].isLeaf = this.isLeaf
}
TreeNode.prototype.changeName = function (name) {
this.name = name
this.updateRecordProperty()
Record[this.id].modify = true
}
TreeNode.prototype.addChildren = function (children, isNew) {
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
if (isNew) {
child.updateRecordProperty()
Record[child.id].add = true
// other params
for (var k in data) {
if (k !== 'id' && k !== 'children' && k !== 'isLeaf') {
this[k] = data[k]
}
}
this.children.concat(children)
} else {
const child = children
child.parent = this
child.pid = this.id
this.children.push(child)
}
if (isNew) {
child.updateRecordProperty()
Record[child.id].add = true
changeName(name) {
this.name = name
}
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.push(child)
}
}
}
// remove self
TreeNode.prototype.remove = function () {
const parent = this.parent
const index = parent.findChildIndex(this)
parent.children.splice(index, 1)
// remove self
remove() {
const parent = this.parent
const index = parent.findChildIndex(this)
parent.children.splice(index, 1)
}
this.updateRecordProperty()
Record[this.id].remove = true
}
// 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
// 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
}
}
}
}
TreeNode.prototype.isTargetChild = function (target) {
let parent = target.parent
while (parent) {
if (parent === this) {
return true
isTargetChild(target) {
let parent = target.parent
while (parent) {
if (parent === this) {
return true
}
parent = parent.parent
}
parent = parent.parent
}
return false
}
TreeNode.prototype.moveInto = function (target) {
if (this.name === 'root' || this === target) {
return
}
// 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)
this.updateRecordProperty()
Record[this.id].targetId = target.id
Record[this.id].move = true
Record[this.id].moveType = 'inside'
}
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
}
}
return index
}
TreeNode.prototype._beforeInsert = function (target) {
if (this.name === 'root' || this === target) {
return false
}
// cannot move 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)
this.updateRecordProperty()
Record[this.id].targetId = target.id
Record[this.id].move = true
Record[this.id].moveType = 'before'
}
TreeNode.prototype.insertAfter = function (target) {
if (!this._beforeInsert(target)) return
const pos = target.parent.findChildIndex(target)
target.parent.children.splice(pos + 1, 0, this)
this.updateRecordProperty()
Record[this.id].targetId = target.id
Record[this.id].move = true
Record[this.id].moveType = 'after'
}
function Tree(data) {
this.root = new TreeNode('root', false, 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.name, _data.isLeaf, _data.id)
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
exports.Record = Record
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)
}
}
}

View File

@@ -1,354 +1,481 @@
<template>
<div>
<div v-if="model.name !== 'root'">
<div class="border up" :class="{'active': isDragEnterUp}"
@drop="dropUp"
<div class="vtl">
<div
v-if="model.name !== 'root'"
:id="model.id"
class="vtl-node"
:class="{ 'vtl-leaf-node': model.isLeaf, 'vtl-tree-node': !model.isLeaf }"
>
<div
class="vtl-border vtl-up"
:class="{ 'vtl-active': isDragEnterUp }"
@drop="dropBefore"
@dragenter="dragEnterUp"
@dragover='dragOverUp'
@dragleave="dragLeaveUp"></div>
<div class='tree-node' :id='model.id' :class="{'active': isDragEnterNode}"
draggable="true"
@click=""
@dragstart='dragStart'
@dragover='dragOver'
@dragenter='dragEnter'
@dragleave='dragLeave'
@drop='drop'
@dragend='dragEnd'
@mouseover='mouseOver'
@mouseout='mouseOut'>
<span class="caret icon is-small" v-if="model.children && model.children.length > 0">
<i class="vue-tree-icon" :class="caretClass" @click.prevent.stop="toggle"></i>
@dragover="dragOverUp"
@dragleave="dragLeaveUp"
/>
<div
:class="treeNodeClass"
:draggable="!model.dragDisabled"
@dragstart="dragStart"
@dragover="dragOver"
@dragenter="dragEnter"
@dragleave="dragLeave"
@drop="drop"
@dragend="dragEnd"
@mouseover="mouseOver"
@mouseout="mouseOut"
@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>
<span v-if="model.isLeaf">
<slot name="leafNodeIcon">
<i class="vue-tree-icon item-icon icon-file"></i>
<slot name="leafNodeIcon" :expanded="expanded" :model="model" :root="rootNode">
<i class="vtl-icon vtl-menu-icon vtl-icon-file"></i>
</slot>
</span>
<span v-else>
<slot name="treeNodeIcon">
<i class="vue-tree-icon item-icon icon-folder"></i>
<slot name="treeNodeIcon" :expanded="expanded" :model="model" :root="rootNode">
<i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i>
</slot>
</span>
<div class="node-content" v-if="!editable">
{{model.name}}
<div class="vtl-node-content" v-if="!editable">
<slot name="leafNameDisplay" :expanded="expanded" :model="model" :root="rootNode">
{{ model.name }}
</slot>
</div>
<input v-else class="vue-tree-input" type="text" ref="nodeInput" :value="model.name" @input="updateName" @blur="setUnEditable">
<div class="operation" v-show="isHover">
<span title="add tree node" @click.stop.prevent="addChild(false)" v-if="!model.isLeaf">
<slot name="addTreeNode">
<i class="vue-tree-icon icon-folder-plus-e"></i>
<input
v-else
class="vtl-input"
type="text"
ref="nodeInput"
:value="model.name"
@input="updateName"
@blur="setUnEditable"
/>
<div class="vtl-operation" v-show="isHover">
<span
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>
</slot>
</span>
<span title="add tree node" @click.stop.prevent="addChild(true)" v-if="!model.isLeaf">
<slot name="addLeafNode">
<i class="vue-tree-icon icon-plus"></i>
<span
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>
</slot>
</span>
<span title="edit" @click.stop.prevent="setEditable">
<slot name="edit">
<i class="vue-tree-icon icon-edit"></i>
<span 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>
</slot>
</span>
<span title="delete" @click.stop.prevent="delNode">
<slot name="edit">
<i class="vue-tree-icon icon-trash"></i>
<span 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>
</slot>
</span>
</div>
</div>
<div v-if="model.children && model.children.length > 0 && expanded"
class="border bottom"
:class="{'active': isDragEnterBottom}"
@drop="dropBottom"
<div
v-if="model.children && model.children.length > 0 && expanded"
class="vtl-border vtl-bottom"
:class="{ 'vtl-active': isDragEnterBottom }"
@drop="dropAfter"
@dragenter="dragEnterBottom"
@dragover='dragOverBottom'
@dragleave="dragLeaveBottom"></div>
@dragover="dragOverBottom"
@dragleave="dragLeaveBottom"
></div>
</div>
<div :class="{'tree-margin': model.name !== 'root'}" v-show="expanded" v-if="isFolder">
<item v-for="model in model.children"
<div
: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-leaf-node-name="defaultLeafNodeName"
:default-expanded="defaultExpanded"
:model="model"
:key='model.id'>
:key="model.id"
>
<template v-slot:leafNameDisplay="slotProps">
<slot name="leafNameDisplay" v-bind="slotProps" />
</template>
<template v-slot:addTreeNodeIcon="slotProps">
<slot name="addTreeNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:addLeafNodeIcon="slotProps">
<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>
</div>
</div>
</template>
<script>
import { Tree, TreeNode } from './Tree.js'
import $ from 'jquery'
import { TreeNode } from './Tree.js'
import { addHandler, removeHandler } from './tools.js'
let compInOperation = null
export default {
name: 'vue-tree-list',
data: function() {
return {
isHover: false,
editable: false,
isDragEnterUp: false,
isDragEnterBottom: false,
isDragEnterNode: false,
expanded: this.defaultExpanded
}
},
props: {
model: {
type: Object
},
defaultLeafNodeName: {
type: String,
default: 'New leaf node'
},
defaultTreeNodeName: {
type: String,
default: 'New tree node'
},
defaultExpanded: {
type: Boolean,
default: true
}
},
computed: {
rootNode() {
var node = this.$parent
while (node._props.model.name !== 'root') {
node = node.$parent
}
return node
},
caretClass() {
return this.expanded ? 'vtl-icon-caret-down' : 'vtl-icon-caret-right'
},
isFolder() {
return this.model.children && this.model.children.length
},
treeNodeClass() {
const {
model: { dragDisabled, disabled },
isDragEnterNode
} = this
let fromComp = ''
export default {
data: function () {
return {
isHover: false,
editable: false,
isDragEnterUp: false,
isDragEnterBottom: false,
isDragEnterNode: false,
expanded: true
'vtl-node-main': true,
'vtl-active': isDragEnterNode,
'vtl-drag-disabled': dragDisabled,
'vtl-disabled': disabled
}
},
props: {
model: {
type: Object
},
defaultLeafNodeName: {
type: String,
default: 'New leaf node'
},
defaultTreeNodeName: {
type: String,
default: 'New tree node'
}
},
beforeCreate() {
this.$options.components.item = require('./VueTreeList').default
},
mounted() {
const vm = this
addHandler(window, 'keyup', function(e) {
// click enter
if (e.keyCode === 13 && vm.editable) {
vm.editable = false
}
},
computed: {
itemIconClass () {
return this.model.isLeaf ? 'icon-file' : 'icon-folder'
},
caretClass () {
return this.expanded ? 'icon-caret-down' : 'icon-caret-right'
},
isFolder() {
return this.model.children &&
this.model.children.length
}
},
mounted () {
const vm = this
$(window).on('keyup', function (e) {
// click enter
if (e.keyCode === 13 && vm.editable) {
vm.editable = false
}
})
},
beforeDestroy() {
removeHandler(window, 'keyup')
},
methods: {
updateName(e) {
var oldName = this.model.name
this.model.changeName(e.target.value)
this.rootNode.$emit('change-name', {
id: this.model.id,
oldName: oldName,
newName: e.target.value,
node: this.model
})
},
beforeDestroy () {
$(window).off('keyup')
delNode() {
this.rootNode.$emit('delete-node', this.model)
},
methods: {
updateName (e) {
this.model.changeName(e.target.value)
},
delNode () {
const vm = this
if (window.confirm('Are you sure?')) {
vm.model.remove()
}
},
setEditable() {
this.editable = true
this.$nextTick(() => {
const $input = this.$refs.nodeInput
$input.focus()
$input.setSelectionRange(0, $input.value.length)
})
},
setEditable () {
this.editable = true
this.$nextTick(() => {
$(this.$refs.nodeInput).trigger('focus')
})
},
setUnEditable(e) {
this.editable = false
var oldName = this.model.name
this.model.changeName(e.target.value)
this.rootNode.$emit('change-name', {
id: this.model.id,
oldName: oldName,
newName: e.target.value,
eventType: 'blur'
})
},
setUnEditable () {
this.editable = false
},
toggle() {
if (this.isFolder) {
this.expanded = !this.expanded
}
},
mouseOver(e) {
this.isHover = true
},
mouseOut(e) {
this.isHover = false
},
addChild(isLeaf) {
const name = isLeaf ? this.defaultLeafNodeName : this.defaultTreeNodeName
this.expanded = true
var node = new TreeNode(name, isLeaf)
this.model.addChildren(node, true)
},
dragStart(e) {
fromComp = this
e.dataTransfer.effectAllowed = 'move'
return true
},
dragEnd(e) {
fromComp = null
},
dragOver(e) {
e.preventDefault()
return true
},
dragEnter(e) {
if (this.model.isLeaf) {
return
}
this.isDragEnterNode = true
},
dragLeave(e) {
this.isDragEnterNode = false
},
drop(e) {
fromComp.model.moveInto(this.model)
this.isDragEnterNode = false
},
dragEnterUp () {
this.isDragEnterUp = true
},
dragOverUp (e) {
e.preventDefault()
return true
},
dragLeaveUp () {
this.isDragEnterUp = false
},
dropUp () {
fromComp.model.insertBefore(this.model)
this.isDragEnterUp = false
},
dragEnterBottom () {
this.isDragEnterBottom = true
},
dragOverBottom (e) {
e.preventDefault()
return true
},
dragLeaveBottom () {
this.isDragEnterBottom = false
},
dropBottom () {
fromComp.model.insertAfter(this.model)
this.isDragEnterBottom = false
toggle() {
if (this.isFolder) {
this.expanded = !this.expanded
}
},
beforeCreate () {
this.$options.components.item = require('./VueTreeList.vue')
mouseOver() {
if (this.model.disabled) return
this.isHover = true
},
mouseOut() {
this.isHover = false
},
click() {
this.rootNode.$emit('click', this.model)
},
addChild(isLeaf) {
const name = isLeaf ? this.defaultLeafNodeName : this.defaultTreeNodeName
this.expanded = true
var node = new TreeNode({ name, isLeaf })
this.model.addChildren(node, true)
this.rootNode.$emit('add-node', node)
},
dragStart(e) {
if (!(this.model.dragDisabled || this.model.disabled)) {
compInOperation = this
// for firefox
e.dataTransfer.setData('data', 'data')
e.dataTransfer.effectAllowed = 'move'
return true
}
return false
},
dragEnd() {
compInOperation = null
},
dragOver(e) {
e.preventDefault()
return true
},
dragEnter() {
if (!compInOperation) return
if (compInOperation.model.id === this.model.id || !compInOperation || this.model.isLeaf)
return
this.isDragEnterNode = true
},
dragLeave() {
this.isDragEnterNode = false
},
drop() {
if (!compInOperation) return
const oldParent = compInOperation.model.parent
compInOperation.model.moveInto(this.model)
this.isDragEnterNode = false
this.rootNode.$emit('drop', {
target: this.model,
node: compInOperation.model,
src: oldParent
})
},
dragEnterUp() {
if (!compInOperation) return
this.isDragEnterUp = true
},
dragOverUp(e) {
e.preventDefault()
return true
},
dragLeaveUp() {
if (!compInOperation) return
this.isDragEnterUp = false
},
dropBefore() {
if (!compInOperation) return
const oldParent = compInOperation.model.parent
compInOperation.model.insertBefore(this.model)
this.isDragEnterUp = false
this.rootNode.$emit('drop-before', {
target: this.model,
node: compInOperation.model,
src: oldParent
})
},
dragEnterBottom() {
if (!compInOperation) return
this.isDragEnterBottom = true
},
dragOverBottom(e) {
e.preventDefault()
return true
},
dragLeaveBottom() {
if (!compInOperation) return
this.isDragEnterBottom = false
},
dropAfter() {
if (!compInOperation) return
const oldParent = compInOperation.model.parent
compInOperation.model.insertAfter(this.model)
this.isDragEnterBottom = false
this.rootNode.$emit('drop-after', {
target: this.model,
node: compInOperation.model,
src: oldParent
})
}
}
}
</script>
<style lang="less" rel="stylesheet/less" scoped>
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?ui1hbx');
src: url('fonts/icomoon.eot?ui1hbx#iefix') format('embedded-opentype'),
<style lang="less" rel="stylesheet/less">
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?ui1hbx');
src: url('fonts/icomoon.eot?ui1hbx#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?ui1hbx') format('truetype'),
url('fonts/icomoon.woff?ui1hbx') format('woff'),
url('fonts/icomoon.svg?ui1hbx#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
font-weight: normal;
font-style: normal;
}
.vue-tree-icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
.vtl-icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&.item-icon {
margin-right: 4px;
&:hover {
color: inherit;
}
}
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&.vtl-menu-icon {
margin-right: 4px;
&:hover {
color: blue;
color: inherit;
}
}
&:hover {
color: blue;
}
}
.icon-file:before {
content: "\e906";
}
.icon-folder:before {
content: "\e907";
}
.icon-caret-down:before {
content: "\e900";
}
.icon-caret-right:before {
content: "\e901";
}
.icon-edit:before {
content: "\e902";
}
.icon-folder-plus-e:before {
content: "\e903";
}
.icon-plus:before {
content: "\e904";
}
.icon-trash:before {
content: "\e905";
}
.vtl-icon-file:before {
content: '\e906';
}
.vtl-icon-folder:before {
content: '\e907';
}
.vtl-icon-caret-down:before {
content: '\e901';
}
.vtl-icon-caret-right:before {
content: '\e900';
}
.vtl-icon-edit:before {
content: '\e902';
}
.vtl-icon-folder-plus-e:before {
content: '\e903';
}
.vtl-icon-plus:before {
content: '\e904';
}
.vtl-icon-trash:before {
content: '\e905';
}
.border {
height: 5px;
&.up {
margin-top: -5px;
background-color: transparent;
}
&.bottom {
background-color: transparent;
}
&.active {
border-bottom: 3px dashed blue;
/*background-color: blue;*/
}
.vtl-border {
height: 5px;
&.vtl-up {
margin-top: -5px;
background-color: transparent;
}
.tree-node {
display: flex;
align-items: center;
padding: 5px 0 5px 1rem;
.input {
border: none;
max-width: 150px;
border-bottom: 1px solid blue;
}
&:hover {
background-color: #f0f0f0;
}
&.active {
outline: 2px dashed pink;
}
.caret {
margin-left: -1rem;
}
.operation {
margin-left: 2rem;
letter-spacing: 1px;
}
&.vtl-bottom {
background-color: transparent;
}
.item {
cursor: pointer;
&.vtl-active {
border-bottom: 3px dashed blue;
/*background-color: blue;*/
}
.tree-margin {
margin-left: 2em;
}
.vtl-node-main {
display: flex;
align-items: center;
padding: 5px 0 5px 1rem;
.vtl-input {
border: none;
max-width: 150px;
border-bottom: 1px solid blue;
}
&:hover {
background-color: #f0f0f0;
}
&.vtl-active {
outline: 2px dashed pink;
}
.vtl-caret {
margin-left: -1rem;
}
.vtl-operation {
margin-left: 2rem;
letter-spacing: 1px;
}
}
.vtl-item {
cursor: pointer;
}
.vtl-tree-margin {
margin-left: 2em;
}
</style>

View File

@@ -1,7 +1,13 @@
/**
* Created by ayou on 17/7/21.
*/
exports.VueTreeList = require('./VueTreeList.vue')
exports.TreeNode = require('./Tree.js').TreeNode
exports.Tree = require('./Tree.js').Tree
exports.Record = require('./Tree.js').Record
import VueTreeList from './VueTreeList'
import { Tree, TreeNode } from './Tree'
VueTreeList.install = Vue => {
Vue.component(VueTreeList.name, VueTreeList)
}
export default VueTreeList
export { Tree, TreeNode, VueTreeList }

45
src/tools.js Normal file
View File

@@ -0,0 +1,45 @@
/**
* Created by ayou on 18/2/6.
*/
var handlerCache
export const addHandler = function(element, type, handler) {
handlerCache = handler
if (element.addEventListener) {
element.addEventListener(type, handler, false)
} else if (element.attachEvent) {
element.attachEvent('on' + type, handler)
} else {
element['on' + type] = handler
}
}
export const removeHandler = function(element, type) {
if (element.removeEventListener) {
element.removeEventListener(type, handlerCache, false)
} else if (element.detachEvent) {
element.detachEvent('on' + type, handlerCache)
} else {
element['on' + type] = null
}
}
// 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
}

View File

@@ -0,0 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render render correctly 1`] = `
<div class="vtl">
<!---->
<div class="">
<div class="vtl">
<div id="1" class="vtl-node vtl-tree-node">
<div class="vtl-border vtl-up"></div>
<div draggable="false" class="vtl-node-main vtl-drag-disabled"><span class="vtl-caret vtl-is-small"><i class="vtl-icon vtl-icon-caret-right"></i></span> <span><i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i></span>
<div class="vtl-node-content">
Node 1
</div>
<div class="vtl-operation" style="display: none;">
<!---->
<!---->
<!---->
<!---->
</div>
</div>
<!---->
</div>
<div class="vtl-tree-margin" style="display: none;">
<div class="vtl">
<div id="2" class="vtl-node vtl-leaf-node">
<div class="vtl-border vtl-up"></div>
<div draggable="true" class="vtl-node-main">
<!----> <span><i class="vtl-icon vtl-menu-icon vtl-icon-file"></i></span>
<div class="vtl-node-content">
Node 1-2
</div>
<div class="vtl-operation" style="display: none;">
<!---->
<!----> <span title="edit"><i class="vtl-icon vtl-icon-edit"></i></span> <span title="delete"><i class="vtl-icon vtl-icon-trash"></i></span></div>
</div>
<!---->
</div>
<!---->
</div>
</div>
</div>
<div class="vtl">
<div id="3" class="vtl-node vtl-tree-node">
<div class="vtl-border vtl-up"></div>
<div draggable="true" class="vtl-node-main vtl-disabled">
<!----> <span><i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i></span>
<div class="vtl-node-content">
Node 2
</div>
<div class="vtl-operation" style="display: none;"><span title="add tree node"><i class="vtl-icon vtl-icon-folder-plus-e"></i></span> <span title="add leaf node"><i class="vtl-icon vtl-icon-plus"></i></span> <span title="edit"><i class="vtl-icon vtl-icon-edit"></i></span> <span title="delete"><i class="vtl-icon vtl-icon-trash"></i></span></div>
</div>
<!---->
</div>
<!---->
</div>
<div class="vtl">
<div id="4" class="vtl-node vtl-tree-node">
<div class="vtl-border vtl-up"></div>
<div draggable="true" class="vtl-node-main">
<!----> <span><i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i></span>
<div class="vtl-node-content">
Node 3
</div>
<div class="vtl-operation" style="display: none;"><span title="add tree node"><i class="vtl-icon vtl-icon-folder-plus-e"></i></span> <span title="add leaf node"><i class="vtl-icon vtl-icon-plus"></i></span> <span title="edit"><i class="vtl-icon vtl-icon-edit"></i></span> <span title="delete"><i class="vtl-icon vtl-icon-trash"></i></span></div>
</div>
<!---->
</div>
<!---->
</div>
</div>
</div>
`;

View 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>
`;

92
tests/unit/drag.spec.js Normal file
View File

@@ -0,0 +1,92 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import { Tree, VueTreeList } from '@/index'
describe('Drag', () => {
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 1-2',
id: 't12',
pid: 't1'
}
]
},
{
name: 'Node 2',
id: 't2',
pid: 0
},
{
name: 'Node 3',
id: 't3',
pid: 0
}
])
wrapper = mount(VueTreeList, { propsData: { model: new Tree([]) } })
wrapper.setProps({ model: tree })
})
it('drag before', done => {
const $tree2 = wrapper.find('#t2 .vtl-node-main')
const $tree1Up = wrapper.find('#t1 .vtl-up')
$tree2.trigger('dragstart', { dataTransfer: { setData: () => {} } })
$tree1Up.trigger('drop')
Vue.nextTick(() => {
expect(wrapper.find('.vtl-node').attributes('id')).toBe('t2')
done()
})
})
it('drag after', done => {
const $tree3 = wrapper.find('#t3 .vtl-node-main')
const $tree1Bottom = wrapper.find('#t1 .vtl-bottom')
$tree3.trigger('dragstart', { dataTransfer: { setData: () => {} } })
$tree1Bottom.trigger('drop')
Vue.nextTick(() => {
expect(
wrapper
.findAll('.vtl-tree-node')
.at(2)
.attributes('id')
).toBe('t3')
done()
})
})
it('drag into', done => {
const $tree3 = wrapper.find('#t3 .vtl-node-main')
const $tree1 = wrapper.find('#t1 .vtl-node-main')
$tree3.trigger('dragstart', { dataTransfer: { setData: () => {} } })
$tree1.trigger('drop')
Vue.nextTick(() => {
expect(wrapper.find('#t1 + .vtl-tree-margin .vtl-node').attributes('id')).toBe('t3')
done()
})
})
it('cannot drag ancestor into child', done => {
const snapshot = wrapper.html()
const $tree1 = wrapper.find('#t1 .vtl-node-main')
const $tree1Child = wrapper.find('#t12 .vtl-node-main')
$tree1.trigger('dragstart', { dataTransfer: { setData: () => {} } })
$tree1Child.trigger('drop')
Vue.nextTick(() => {
expect(wrapper.html()).toBe(snapshot)
done()
})
})
})

View File

@@ -0,0 +1,86 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import { Tree, VueTreeList } from '@/index'
describe('Operation', () => {
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([]) } })
wrapper.setProps({ model: tree })
})
it('delete leaf node', done => {
const $node11Trash = wrapper.find('#t11 [title="delete"]')
$node11Trash.trigger('click')
wrapper.emitted('delete-node')[0][0].remove()
Vue.nextTick(() => {
expect(wrapper.findAll('.vtl-node').length).toBe(2)
done()
})
})
it('delete tree node', done => {
const $node11Trash = wrapper.find('#t1 [title="delete"]')
$node11Trash.trigger('click')
wrapper.emitted('delete-node')[0][0].remove()
Vue.nextTick(() => {
expect(wrapper.findAll('.vtl-node').length).toBe(1)
done()
})
})
it('add leaf node', done => {
const $node1AddLeafNode = wrapper.find('#t1 [title="add leaf node"]')
$node1AddLeafNode.trigger('click')
Vue.nextTick(() => {
expect(wrapper.findAll('.vtl-leaf-node').length).toBe(2)
done()
})
})
it('add tree node', done => {
const $node1AddTreeNode = wrapper.find('#t1 [title="add tree node"]')
$node1AddTreeNode.trigger('click')
Vue.nextTick(() => {
expect(wrapper.findAll('.vtl-tree-node').length).toBe(3)
done()
})
})
it('change node name', done => {
const $node1Edit = wrapper.find('#t1 [title="edit"]')
$node1Edit.trigger('click')
Vue.nextTick(() => {
const $input = wrapper.find('#t1 .vtl-input')
$input.element.value = 'New Node 1'
$input.trigger('input')
var event = new KeyboardEvent('keyup', { keyCode: 13 })
window.dispatchEvent(event)
Vue.nextTick(() => {
expect(wrapper.find('#t1').text()).toBe('New Node 1')
done()
})
})
})
})

49
tests/unit/render.spec.js Normal file
View File

@@ -0,0 +1,49 @@
import { mount } from '@vue/test-utils'
import { Tree, VueTreeList } from '@/index'
describe('Render', () => {
it('render correctly', () => {
const tree = new Tree([
{
name: 'Node 1',
id: 1,
pid: 0,
dragDisabled: true,
addTreeNodeDisabled: true,
addLeafNodeDisabled: true,
editNodeDisabled: true,
delNodeDisabled: true,
children: [
{
name: 'Node 1-2',
id: 2,
isLeaf: true,
pid: 1
}
]
},
{
name: 'Node 2',
id: 3,
pid: 0,
disabled: true
},
{
name: 'Node 3',
id: 4,
pid: 0
}
])
const wrapper = mount(VueTreeList, {
propsData: {
model: tree,
defaultTreeNodeName: 'new node',
defaultLeafNodeName: 'new leaf',
defaultExpanded: false
}
})
expect(wrapper).toMatchSnapshot()
})
})

91
tests/unit/slot.spec.js Normal file
View File

@@ -0,0 +1,91 @@
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
View File

@@ -0,0 +1,5 @@
module.exports = {
css: {
extract: false
}
}

View File

@@ -1,51 +0,0 @@
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'vue-tree-list.min.js',
library: 'VueTreeList',
libraryTarget: 'umd'
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel',
include: [path.join(__dirname, 'src')],
exclude: /node_modules/
},
{
test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]'
}
}
]
},
vue: {
loaders: {
less: 'vue-style!css!less'
},
postcss: [
require('autoprefixer')({
browsers: ['iOS >= 7', 'Android >= 4.1']
})
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
}