Compare commits

...

52 Commits

Author SHA1 Message Date
ayou
fab494cedf Create LICENSE 2019-12-30 22:46:31 +08:00
youxingzhi
ad6ad1b255 1.4.1 2019-12-30 21:51:20 +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
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
27 changed files with 14444 additions and 339 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"
}

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:unit

21
.gitignore vendored
View File

@@ -1,4 +1,21 @@
.DS_Store
node_modules 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 .idea
coverage .vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

4
.huskyrc.yml Normal file
View File

@@ -0,0 +1,4 @@
hooks:
pre-commit:
- npm run standard
commit-msg: commitlint -E HUSKY_GIT_PARAMS

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.

5
babel.config.js Normal file
View File

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

View File

@@ -1,39 +0,0 @@
var webpack = require('webpack');
var path = require('path');
var projectRoot = path.resolve(__dirname, '../')
module.exports = {
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel',
include: [path.join(projectRoot, 'src'), path.join(projectRoot, 'dev')],
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']
})
]
}
}

View File

@@ -1,23 +0,0 @@
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseConfig = require('./webpack.base.conf')
var path = require('path')
var projectRoot = path.resolve(__dirname, '../')
module.exports = merge(baseConfig, {
entry: './src/index.js',
output: {
path: path.resolve(projectRoot, 'dist'),
filename: 'vue-tree-list.min.js',
library: 'VueTreeList',
libraryTarget: 'umd'
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
})

View File

@@ -1,28 +0,0 @@
var webpack = require('webpack')
var merge = require('webpack-merge')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var baseConfig = require('./webpack.base.conf')
var path = require('path')
var projectRoot = path.resolve(__dirname, '../')
module.exports = merge(baseConfig, {
entry: './dev/index.js',
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true,
open: true,
hot: true,
inline: true
},
devtool: '#eval-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './dev/index.html',
filename: 'index.html',
inject: true
}),
new webpack.HotModuleReplacementPlugin()
]
})

View File

@@ -1,7 +1,25 @@
<template> <template>
<div> <div>
<button @click="addNode">Add Node</button> <button @click="addNode">Add Node</button>
<vue-tree-list @click="onClick" :model="data" default-tree-node-name="new node" default-leaf-node-name="new leaf"></vue-tree-list> <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">
<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> <button @click="getNewTree">Get new tree</button>
<pre> <pre>
{{newTree}} {{newTree}}
@@ -23,6 +41,10 @@
id: 1, id: 1,
pid: 0, pid: 0,
dragDisabled: true, dragDisabled: true,
addTreeNodeDisabled: true,
addLeafNodeDisabled: true,
editNodeDisabled: true,
delNodeDisabled: true,
children: [ children: [
{ {
name: 'Node 1-2', name: 'Node 1-2',
@@ -47,13 +69,42 @@
} }
}, },
methods: { methods: {
addNode: function () { onDel (node) {
console.log(node)
node.remove()
},
onChangeName (params) {
console.log(params)
},
onAddNode (params) {
console.log(params)
},
onClick (params) {
console.log(params)
},
drop: function ({node, src, target}) {
console.log('drop', node, src, target)
},
dropBefore: function ({node, src, target}) {
console.log('drop-before', node, src, target)
},
dropAfter: function ({node, src, target}) {
console.log('drop-after', node, src, target)
},
addNode () {
var node = new TreeNode({ name: 'new node', isLeaf: false }) var node = new TreeNode({ name: 'new node', isLeaf: false })
if (!this.data.children) this.data.children = [] if (!this.data.children) this.data.children = []
this.data.addChildren(node) this.data.addChildren(node)
}, },
getNewTree: function () { getNewTree () {
var vm = this var vm = this
function _dfs (oldNode) { function _dfs (oldNode) {
var newNode = {} var newNode = {}
@@ -74,10 +125,6 @@
} }
vm.newTree = _dfs(vm.data) vm.newTree = _dfs(vm.data)
},
onClick(model) {
console.log(model)
} }
} }
} }
@@ -95,3 +142,11 @@
} }
} }
</style> </style>
<style lang="less" rel="stylesheet/less" scoped>
.icon {
&:hover {
cursor: pointer;
}
}
</style>

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="with=device-width,initial-scale=1.0,
maximum-scale=1.0,minimum-scale=1.0,uses-scalable=no">
<title>vue-date-range</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -6,4 +6,4 @@ import App from './App.vue'
new Vue({ new Vue({
render: h => h(App) render: h => h(App)
}).$mount('#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> </style>
</head> </head>
<body> <body>
@@ -141,7 +162,7 @@
name: 'Node 2', name: 'Node 2',
id: 3, id: 3,
pid: 0, pid: 0,
dragDisabled: true disabled: true
}, },
{ {
name: 'Node 3', name: 'Node 3',

4
jest.config.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
preset: "@vue/cli-plugin-unit-jest",
snapshotSerializers: ["jest-serializer-vue"]
};

13806
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,59 +1,77 @@
{ {
"name": "vue-tree-list", "name": "vue-tree-list",
"version": "1.1.0", "version": "1.4.1",
"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.",
"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 --config build/webpack.build.conf.js",
"dev": "webpack-dev-server --config build/webpack.dev.conf.js",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ParadeTo/vue-tree-list.git"
},
"keywords": [
"vue",
"tree"
],
"author": "ayou", "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",
"lint": "vue-cli-service lint",
"commit": "npx git-cz",
"prepublish": "npm run build"
},
"main": "dist/vue-tree-list.umd.min.js",
"dependencies": {
"core-js": "^3.4.3",
"vue": "^2.6.10"
},
"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",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"jest-serializer-vue": "^2.0.2",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"vue-template-compiler": "^2.6.10"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {
"no-console": "warn"
},
"parserOptions": {
"parser": "babel-eslint"
},
"overrides": [
{
"files": [
"**/__tests__/*.{j,t}s?(x)",
"**/tests/unit/**/*.spec.{j,t}s?(x)"
],
"env": {
"jest": true
}
}
]
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"bugs": { "bugs": {
"url": "https://github.com/ParadeTo/vue-tree-list/issues" "url": "https://github.com/ParadeTo/vue-tree-list/issues"
}, },
"homepage": "https://github.com/ParadeTo/vue-tree-list#readme", "homepage": "https://github.com/ParadeTo/vue-tree-list#readme",
"devDependencies": { "keywords": [
"autoprefixer": "^6.4.0", "vue",
"babel-core": "^6.0.0", "tree"
"babel-eslint": "^7.0.0", ],
"babel-loader": "^6.0.0", "license": "ISC",
"babel-plugin-istanbul": "^3.0.0", "repository": {
"babel-plugin-transform-runtime": "^6.0.0", "type": "git",
"babel-preset-es2015": "^6.0.0", "url": "git+https://github.com/ParadeTo/vue-tree-list.git"
"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",
"less": "^2.7.2",
"less-loader": "^2.2.3",
"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",
"webpack-dev-server": "1.14.0",
"webpack-merge": "^0.14.1",
"html-webpack-plugin": "^2.8.1"
} }
} }

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>

282
readme.md
View File

@@ -8,118 +8,202 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
# use # use
``npm install vue-tree-list`` ``npm install vue-tree-list``
```javascript ```html
<button @click="addNode">Add Node</button> <template>
<vue-tree-list @click="onClick" :model="data" default-tree-node-name="new node" default-leaf-node-name="new leaf"></vue-tree-list> <div>
<button @click="getNewTree">Get new tree</button> <button @click="addNode">Add Node</button>
<pre> <vue-tree-list
{{newTree}} @click="onClick"
</pre> @change-name="onChangeName"
... @delete-node="onDel"
import { VueTreeList, Tree, TreeNode } from '../src' @add-node="onAddNode"
export default { :model="data"
components: { default-tree-node-name="new node"
VueTreeList default-leaf-node-name="new leaf"
}, v-bind:default-expanded="false">
data () { <span class="icon" slot="addTreeNodeIcon">📂</span>
return { <span class="icon" slot="addLeafNodeIcon"></span>
newTree: {}, <span class="icon" slot="editNodeIcon">📃</span>
data: new Tree([ <span class="icon" slot="delNodeIcon">✂️</span>
{ <span class="icon" slot="leafNodeIcon">🍃</span>
name: 'Node 1', <span class="icon" slot="treeNodeIcon">🌲</span>
id: 1, </vue-tree-list>
pid: 0, <button @click="getNewTree">Get new tree</button>
dragDisabled: true, <pre>
children: [ {{newTree}}
{ </pre>
name: 'Node 1-2', </div>
id: 2, </template>
isLeaf: true,
pid: 1 <script>
} import { VueTreeList, Tree, TreeNode } from 'vue-tree-list'
] export default {
}, components: {
{ VueTreeList
name: 'Node 2',
id: 3,
pid: 0,
dragDisabled: true
},
{
name: 'Node 3',
id: 4,
pid: 0
}
])
}
},
methods: {
addNode: function () {
var node = new TreeNode({ name: 'new node', isLeaf: false })
if (!this.data.children) this.data.children = []
this.data.addChildren(node)
}, },
data () {
getNewTree: function () { return {
var vm = this newTree: {},
function _dfs (oldNode) { data: new Tree([
var newNode = {} {
name: 'Node 1',
for (var k in oldNode) { id: 1,
if (k !== 'children' && k !== 'parent') { pid: 0,
newNode[k] = oldNode[k] 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
} }
} ])
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)
}, },
methods: {
onDel (node) {
console.log(node)
node.remove()
},
onClick(model) { onChangeName (params) {
console.log(model) 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 () {
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;
}
}
</style>
``` ```
# props # props
**default-tree-node-name** ## props of vue-tree-list
| name | type | default | description |
Default name for new treenode. |:-----:|:-------:|:------------:|:----:|
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
**default-leaf-node-name**
Default name for new leafnode. ## 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 # events
**click** | 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
```javascript # customize operation icons
<vue-tree-list @click="onClick" ... The component has default icons for `addTreeNodeIcon`, `addLeafNodeIcon`, `editNodeIcon`, `delNodeIcon`, `leafNodeIcon`, `treeNodeIcon` button, but you can also customize them:
...
onClick(model) { ```html
console.log(model) <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>
``` ```
# Forbid dragging
Use `dragDisabled` to forbid dragging:
```javascript
data: new Tree([
{
name: 'Node 1',
id: 1,
pid: 0,
dragDisabled: true,
...
```

View File

@@ -1,5 +1,3 @@
let nodeId = 1
/** /**
* Tree data struct * Tree data struct
* Created by ayou on 2017/7/20. * Created by ayou on 2017/7/20.
@@ -12,10 +10,8 @@ let nodeId = 1
*/ */
const TreeNode = function (data) { const TreeNode = function (data) {
const { id, isLeaf } = data const { id, isLeaf } = data
// this.name = name this.id = (typeof id === 'undefined') ? new Date().valueOf() : id
this.id = (typeof id === 'undefined') ? ('new' + nodeId++) : id
this.parent = null this.parent = null
// this.pid = null
this.children = null this.children = null
this.isLeaf = !!isLeaf this.isLeaf = !!isLeaf
@@ -31,7 +27,7 @@ TreeNode.prototype.changeName = function (name) {
this.name = name this.name = name
} }
TreeNode.prototype.addChildren = function (children, isNew) { TreeNode.prototype.addChildren = function (children) {
if (!this.children) { if (!this.children) {
this.children = [] this.children = []
} }

View File

@@ -2,7 +2,7 @@
<div class='vtl'> <div class='vtl'>
<div v-if="model.name !== 'root'"> <div v-if="model.name !== 'root'">
<div class="vtl-border vtl-up" :class="{'vtl-active': isDragEnterUp}" <div class="vtl-border vtl-up" :class="{'vtl-active': isDragEnterUp}"
@drop="dropUp" @drop="dropBefore"
@dragenter="dragEnterUp" @dragenter="dragEnterUp"
@dragover='dragOverUp' @dragover='dragOverUp'
@dragleave="dragLeaveUp"></div> @dragleave="dragLeaveUp"></div>
@@ -37,23 +37,23 @@
</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"> <span title="add tree node" @click.stop.prevent="addChild(false)" v-if="!model.isLeaf && !model.addTreeNodeDisabled">
<slot name="addTreeNode"> <slot name="addTreeNodeIcon">
<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 tree node" @click.stop.prevent="addChild(true)" v-if="!model.isLeaf"> <span title="add leaf node" @click.stop.prevent="addChild(true)" v-if="!model.isLeaf && !model.addLeafNodeDisabled">
<slot name="addLeafNode"> <slot name="addLeafNodeIcon">
<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"> <span title="edit" @click.stop.prevent="setEditable" v-if="!model.editNodeDisabled">
<slot name="edit"> <slot name="editNodeIcon">
<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"> <span title="delete" @click.stop.prevent="delNode" v-if="!model.delNodeDisabled">
<slot name="edit"> <slot name="delNodeIcon">
<i class="vtl-icon vtl-icon-trash"></i> <i class="vtl-icon vtl-icon-trash"></i>
</slot> </slot>
</span> </span>
@@ -63,28 +63,35 @@
<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="dropBottom" @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="expanded" v-if="isFolder"> <div :class="{'vtl-tree-margin': model.name !== 'root'}" v-show="model.name === 'root' || expanded" v-if="isFolder">
<item v-for="model in model.children" <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"
v-bind:default-expanded="defaultExpanded"
:model="model" :model="model"
:key='model.id'> :key='model.id'>
<slot name="addTreeNodeIcon" slot="addTreeNodeIcon" />
<slot name="addLeafNodeIcon" slot="addLeafNodeIcon" />
<slot name="editNodeIcon" slot="editNodeIcon" />
<slot name="delNodeIcon" slot="delNodeIcon" />
<slot name="leafNodeIcon" slot="leafNodeIcon" />
<slot name="treeNodeIcon" slot="treeNodeIcon" />
</item> </item>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { Tree, TreeNode } from './Tree.js' import { TreeNode } from './Tree.js'
import { addHandler, removeHandler } from './tools.js' import { addHandler, removeHandler } from './tools.js'
let fromComp = null let compInOperation = null
export default { export default {
data: function () { data: function () {
@@ -94,7 +101,7 @@
isDragEnterUp: false, isDragEnterUp: false,
isDragEnterBottom: false, isDragEnterBottom: false,
isDragEnterNode: false, isDragEnterNode: false,
expanded: true expanded: this.defaultExpanded
} }
}, },
props: { props: {
@@ -108,6 +115,10 @@
defaultTreeNodeName: { defaultTreeNodeName: {
type: String, type: String,
default: 'New tree node' default: 'New tree node'
},
defaultExpanded: {
type: Boolean,
default: true
} }
}, },
computed: { computed: {
@@ -141,6 +152,9 @@
} }
} }
}, },
beforeCreate () {
this.$options.components.item = require('./VueTreeList').default
},
mounted () { mounted () {
const vm = this const vm = this
addHandler(window, 'keyup', function (e) { addHandler(window, 'keyup', function (e) {
@@ -155,21 +169,23 @@
}, },
methods: { methods: {
updateName (e) { updateName (e) {
var oldName = this.model.name;
this.model.changeName(e.target.value) this.model.changeName(e.target.value)
var node = this.getRootNode();
node.$emit('change-name', {'id': this.model.id, 'oldName': oldName, 'newName': e.target.value})
}, },
delNode () { delNode () {
const vm = this var node = this.getRootNode()
if (window.confirm('Are you sure?')) { node.$emit('delete-node', this.model)
vm.model.remove()
}
}, },
setEditable () { setEditable () {
this.editable = true this.editable = true
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.nodeInput.focus() const $input = this.$refs.nodeInput
// fireFocusEvent(this.$refs.nodeInput) $input.focus()
$input.setSelectionRange(0, $input.value.length)
}) })
}, },
@@ -183,22 +199,18 @@
} }
}, },
mouseOver(e) { mouseOver() {
if (this.model.disabled) return if (this.model.disabled) return
this.isHover = true this.isHover = true
}, },
mouseOut(e) { mouseOut() {
this.isHover = false this.isHover = false
}, },
click() { click() {
var node = this.$parent var node = this.getRootNode()
var clickModel = this.model node.$emit('click', this.model);
while (node._props.model.name !== 'root') {
node = node.$parent
}
node.$emit('click', clickModel)
}, },
addChild(isLeaf) { addChild(isLeaf) {
@@ -206,11 +218,13 @@
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();
root.$emit('add-node', node)
}, },
dragStart(e) { dragStart(e) {
if (!(this.model.dragDisabled || this.model.disabled)) { if (!(this.model.dragDisabled || this.model.disabled)) {
fromComp = 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'
@@ -218,29 +232,32 @@
} }
return false return false
}, },
dragEnd(e) { dragEnd() {
fromComp = null compInOperation = null
}, },
dragOver(e) { dragOver(e) {
e.preventDefault() e.preventDefault()
return true return true
}, },
dragEnter(e) { dragEnter() {
if (!fromComp) return if (!compInOperation) return
if (this.model.isLeaf) return if (this.model.isLeaf) return
this.isDragEnterNode = true this.isDragEnterNode = true
}, },
dragLeave(e) { dragLeave() {
this.isDragEnterNode = false this.isDragEnterNode = false
}, },
drop(e) { drop() {
if (!fromComp) return if (!compInOperation) return
fromComp.model.moveInto(this.model) const oldParent = compInOperation.model.parent;
compInOperation.model.moveInto(this.model)
this.isDragEnterNode = false this.isDragEnterNode = false
var node = this.getRootNode();
node.$emit('drop', {target: this.model, node: compInOperation.model, src: oldParent})
}, },
dragEnterUp () { dragEnterUp () {
if (!fromComp) return if (!compInOperation) return
this.isDragEnterUp = true this.isDragEnterUp = true
}, },
dragOverUp (e) { dragOverUp (e) {
@@ -248,17 +265,20 @@
return true return true
}, },
dragLeaveUp () { dragLeaveUp () {
if (!fromComp) return if (!compInOperation) return
this.isDragEnterUp = false this.isDragEnterUp = false
}, },
dropUp () { dropBefore () {
if (!fromComp) return if (!compInOperation) return
fromComp.model.insertBefore(this.model) const oldParent = compInOperation.model.parent;
compInOperation.model.insertBefore(this.model)
this.isDragEnterUp = false this.isDragEnterUp = false
var node = this.getRootNode();
node.$emit('drop-before', {target: this.model, node: compInOperation.model, src: oldParent})
}, },
dragEnterBottom () { dragEnterBottom () {
if (!fromComp) return if (!compInOperation) return
this.isDragEnterBottom = true this.isDragEnterBottom = true
}, },
dragOverBottom (e) { dragOverBottom (e) {
@@ -266,17 +286,24 @@
return true return true
}, },
dragLeaveBottom () { dragLeaveBottom () {
if (!fromComp) return if (!compInOperation) return
this.isDragEnterBottom = false this.isDragEnterBottom = false
}, },
dropBottom () { dropAfter () {
if (!fromComp) return if (!compInOperation) return
fromComp.model.insertAfter(this.model) const oldParent = compInOperation.model.parent;
compInOperation.model.insertAfter(this.model)
this.isDragEnterBottom = false this.isDragEnterBottom = false
var node = this.getRootNode();
node.$emit('drop-after', {target: this.model, node: compInOperation.model, src: oldParent})
},
getRootNode() {
var node = this.$parent
while (node._props.model.name !== 'root') {
node = node.$parent
}
return node;
} }
},
beforeCreate () {
this.$options.components.item = require('./VueTreeList.vue')
} }
} }
</script> </script>

View File

@@ -1,7 +1,8 @@
/** /**
* Created by ayou on 17/7/21. * Created by ayou on 17/7/21.
*/ */
exports.VueTreeList = require('./VueTreeList.vue')
exports.TreeNode = require('./Tree.js').TreeNode import VueTreeList from "./VueTreeList";
exports.Tree = require('./Tree.js').Tree import { Tree, TreeNode } from "./Tree";
// exports.Record = require('./Tree.js').Record
export { Tree, TreeNode, VueTreeList };

View File

@@ -4,28 +4,28 @@
var handlerCache var handlerCache
exports.addHandler = function(element, type, handler) { exports.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)
} else if (element.attachEvent) { } else if (element.attachEvent) {
element.attachEvent("on" + type, handler); element.attachEvent('on' + type, handler)
} else { } else {
element["on" + type] = handler; element['on' + type] = handler
} }
} }
exports.removeHandler = function (element, type) { exports.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) {
element.detachEvent("on" + type, handlerCache); element.detachEvent('on' + type, handlerCache)
} else { } else {
element["on" + type] = null; element['on' + type] = null
} }
} }
// exports.fireFocusEvent = function (ele) { // exports.fireFocusEvent = function (ele) {
// var event = new FocusEvent() // var event = new FocusEvent()
// ele.dispatch(event) // ele.dispatch(event)
// } // }

View File

@@ -0,0 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VueTreeList renders correctly 1`] = `
<div class="vtl">
<!---->
<div class="">
<div class="vtl">
<div>
<div class="vtl-border vtl-up"></div>
<div id="1" draggable="false" class="vtl-tree-node 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>
<div class="vtl-border vtl-up"></div>
<div id="2" draggable="true" class="vtl-tree-node">
<!----> <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>
<div class="vtl-border vtl-up"></div>
<div id="3" draggable="true" class="vtl-tree-node 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>
<div class="vtl-border vtl-up"></div>
<div id="4" draggable="true" class="vtl-tree-node">
<!----> <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,49 @@
import { mount } from "@vue/test-utils";
import { Tree, VueTreeList } from "@/index";
describe("VueTreeList", () => {
it("renders 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();
});
});