Compare commits

...

36 Commits
1.0.0 ... 1.2.0

Author SHA1 Message Date
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
ayou
9f286fde0d 1.0.1 for update npm 2017-07-23 11:08:20 +08:00
19 changed files with 568 additions and 265 deletions

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"
}

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
node_modules
npm-debug.log
.idea
coverage
coverage
package-lock.json

4
.huskyrc.yml Normal file
View File

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

View File

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

View File

@@ -1,14 +1,8 @@
var webpack = require('webpack');
var path = require('path');
var path = require('path')
var projectRoot = path.resolve(__dirname, '../')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'vue-tree-list.min.js',
library: 'VueTreeList',
libraryTarget: 'umd'
},
module: {
loaders: [
{
@@ -18,7 +12,7 @@ module.exports = {
{
test: /\.js$/,
loader: 'babel',
include: [path.join(__dirname, 'src')],
include: [path.join(projectRoot, 'src'), path.join(projectRoot, 'dev')],
exclude: /node_modules/
},
{
@@ -40,12 +34,5 @@ module.exports = {
browsers: ['iOS >= 7', 'Android >= 4.1']
})
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
}
}

View File

@@ -0,0 +1,23 @@
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
}
})
]
})

25
build/webpack.dev.conf.js Normal file
View File

@@ -0,0 +1,25 @@
var webpack = require('webpack')
var merge = require('webpack-merge')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var baseConfig = require('./webpack.base.conf')
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()
]
})

126
dev/App.vue Normal file
View File

@@ -0,0 +1,126 @@
<template>
<div>
<button @click="addNode">Add Node</button>
<vue-tree-list
@click="onClick"
@change-name="onChange"
@delete-node="onDel"
@add-node="onChange"
:model="data"
default-tree-node-name="new node"
default-leaf-node-name="new leaf"
v-bind:default-expanded="false">
<span class="icon" slot="addTreeNode">addTreeNode</span>
<span class="icon" slot="addLeafNode">addLeafNode</span>
<span class="icon" slot="editNode">editNode</span>
<span class="icon" slot="delNode">delNode</span>
</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,
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) {
node.remove()
},
onChange (data) {
console.log(data)
},
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)
},
onClick(model) {
console.log(model)
}
}
}
</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>

11
dev/index.html Normal file
View File

@@ -0,0 +1,11 @@
<!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>

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)
}
}
})

View File

@@ -1,13 +1,17 @@
{
"name": "vue-tree-list",
"version": "1.0.0",
"description": "A vue component for tree structure.",
"version": "1.2.0",
"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": {
"commit": "npx git-cz",
"standard": "standard",
"test": "karma start --single-run",
"unit": "karma start --watch",
"coveralls": "npm run test -- --report lcovonly && cat ./coverage/lcov.info | coveralls",
"build": "webpack"
"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",
@@ -23,10 +27,9 @@
"url": "https://github.com/ParadeTo/vue-tree-list/issues"
},
"homepage": "https://github.com/ParadeTo/vue-tree-list#readme",
"dependencies": {
"jquery": "^3.2.1"
},
"devDependencies": {
"@commitlint/cli": "^7.2.1",
"@commitlint/config-conventional": "^7.1.2",
"autoprefixer": "^6.4.0",
"babel-core": "^6.0.0",
"babel-eslint": "^7.0.0",
@@ -40,27 +43,26 @@
"cross-env": "^5.0.1",
"css-loader": "^0.26.2",
"file-loader": "^0.10.1",
"html-webpack-plugin": "^2.8.1",
"husky": "^1.2.0",
"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",
"standard": "^12.0.1",
"url-loader": "^0.5.7",
"vue": "^2.3.4",
"vue": "^2.6.6",
"vue-loader": "^11.1.3",
"vue-style-loader": "^2.0.3",
"vue-template-compiler": "^2.2.0",
"webpack": "^1.13.2"
"vue-template-compiler": "^2.6.6",
"webpack": "^1.13.2",
"webpack-dev-server": "1.14.0",
"webpack-merge": "^0.14.1"
},
"dependencies": {
"cz-conventional-changelog": "^2.1.0"
}
}

190
readme.md
View File

@@ -9,80 +9,142 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
``npm install vue-tree-list``
```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>
<div>
<button @click="addNode">Add Node</button>
<vue-tree-list
@click="onClick"
@change-name="onChange"
@delete-node="onChange"
@add-node="onChange"
:model="data"
default-tree-node-name="new node"
default-leaf-node-name="new leaf"
v-bind:default-expanded="false">
</vue-tree-list>
<button @click="getNewTree">Get new tree</button>
<pre>
{{newTree}}
</pre>
</div>
...
import { VueTreeList, Tree, TreeNode } from 'vue-tree-list'
export default {
components: {
VueTreeList
},
data () {
return {
newTree: {},
data: new Tree([
{
name: 'Node 1',
id: 1,
pid: 0,
children: [
{
name: 'Node 1-2',
id: 2,
isLeaf: true,
pid: 1
}
]
},
{
name: 'Node 2',
id: 3,
pid: 0
},
{
name: 'Node 3',
id: 4,
pid: 0
}
])
}
},
methods: {
addNode: function () {
var node = new TreeNode('new node', false)
if (!this.data.children) this.data.children = []
this.data.addChildren(node)
},
getNewTree: function () {
const vm = this
function _dfs (oldNode) {
let newNode = {}
newNode.name = oldNode.name
newNode.pid = oldNode.pid
newNode.isLeaf = oldNode.isLeaf
newNode.id = oldNode.id
if (oldNode.children && oldNode.children.length > 0) {
newNode.children = []
for (let i = 0, len = oldNode.children.length; i < len; i++) {
newNode.children.push(_dfs(oldNode.children[i]))
components: {
VueTreeList
},
data () {
return {
newTree: {},
data: new Tree([
{
name: 'Node 1',
id: 1,
pid: 0,
dragDisabled: 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: {
onChange (data) {
console.log(data)
},
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]
}
return newNode
}
vm.newTree = _dfs(vm.data)
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)
},
onClick(model) {
console.log(model)
}
}
}
```
# props
default-tree-node-name: Default name for new treenode.
default-leaf-node-name: Default name for new leafnode.
**default-tree-node-name**
Default name for new treenode.
**default-leaf-node-name**
Default name for new leafnode.
**default-expanded**
Default node is expanded or not.
# events
**click**
**change-name**
**delete-node**
```javascript
@delete-node="onDel"
...
onDel (node) {
node.remove()
},
```
**add-node**
# Forbid dragging
Use `dragDisabled` to forbid dragging:
```javascript
data: new Tree([
{
name: 'Node 1',
id: 1,
pid: 0,
dragDisabled: true,
...
```

View File

@@ -1,43 +1,33 @@
// used to record treenode's change
let Record = {}
// treenode's id
let nodeId = 1
/**
* 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
const TreeNode = function (data) {
const { id, isLeaf } = data
this.id = (typeof id === 'undefined') ? new Date().valueOf() : id
this.parent = null
this.pid = null
this.children = null
this.isLeaf = !!isLeaf
}
TreeNode.prototype.updateRecordProperty = function () {
if (!Record[this.id]) {
Record[this.id] = {}
// other params
for (var k in data) {
if (k !== 'id' && k !== 'children' && k !== 'isLeaf') {
this[k] = data[k]
}
}
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) {
TreeNode.prototype.addChildren = function (children) {
if (!this.children) {
this.children = []
}
@@ -47,11 +37,6 @@ TreeNode.prototype.addChildren = function (children, isNew) {
const child = children[i]
child.parent = this
child.pid = this.id
if (isNew) {
child.updateRecordProperty()
Record[child.id].add = true
}
}
this.children.concat(children)
} else {
@@ -59,11 +44,6 @@ TreeNode.prototype.addChildren = function (children, isNew) {
child.parent = this
child.pid = this.id
this.children.push(child)
if (isNew) {
child.updateRecordProperty()
Record[child.id].add = true
}
}
}
@@ -72,9 +52,6 @@ TreeNode.prototype.remove = function () {
const parent = this.parent
const index = parent.findChildIndex(this)
parent.children.splice(index, 1)
this.updateRecordProperty()
Record[this.id].remove = true
}
// remove child
@@ -120,11 +97,6 @@ TreeNode.prototype.moveInto = function (target) {
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) {
@@ -159,11 +131,6 @@ TreeNode.prototype.insertBefore = function (target) {
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) {
@@ -171,15 +138,10 @@ TreeNode.prototype.insertAfter = function (target) {
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)
function Tree (data) {
this.root = new TreeNode({ name: 'root', isLeaf: false, id: 0 })
this.initNode(this.root, data)
return this.root
}
@@ -188,7 +150,7 @@ 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)
var child = new TreeNode(_data)
if (_data.children && _data.children.length > 0) {
this.initNode(child, _data.children)
}
@@ -198,4 +160,3 @@ Tree.prototype.initNode = function (node, data) {
exports.Tree = Tree
exports.TreeNode = TreeNode
exports.Record = Record

View File

@@ -1,15 +1,13 @@
<template>
<div>
<div class='vtl'>
<div v-if="model.name !== 'root'">
<div class="border up" :class="{'active': isDragEnterUp}"
<div class="vtl-border vtl-up" :class="{'vtl-active': isDragEnterUp}"
@drop="dropUp"
@dragenter="dragEnterUp"
@dragover='dragOverUp'
@dragleave="dragLeaveUp"></div>
<div class='tree-node' :id='model.id' :class="{'active': isDragEnterNode}"
draggable="true"
@click=""
<div :id='model.id' :class="treeNodeClass"
:draggable="!model.dragDisabled"
@dragstart='dragStart'
@dragover='dragOver'
@dragenter='dragEnter'
@@ -17,65 +15,71 @@
@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>
@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>
<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>
<i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i>
</slot>
</span>
<div class="node-content" v-if="!editable">
<div class="vtl-node-content" v-if="!editable">
{{model.name}}
</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">
<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">
<slot name="addTreeNode">
<i class="vue-tree-icon icon-folder-plus-e"></i>
<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">
<span title="add leaf node" @click.stop.prevent="addChild(true)" v-if="!model.isLeaf">
<slot name="addLeafNode">
<i class="vue-tree-icon icon-plus"></i>
<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>
<slot name="editNode">
<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>
<slot name="delNode">
<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}"
class="vtl-border vtl-bottom"
:class="{'vtl-active': isDragEnterBottom}"
@drop="dropBottom"
@dragenter="dragEnterBottom"
@dragover='dragOverBottom'
@dragleave="dragLeaveBottom"></div>
</div>
<div :class="{'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"
:default-tree-node-name="defaultTreeNodeName"
:default-leaf-node-name="defaultLeafNodeName"
v-bind:default-expanded="defaultExpanded"
:model="model"
:key='model.id'>
<slot name="addTreeNode" slot="addTreeNode" />
<slot name="addLeafNode" slot="addLeafNode" />
<slot name="editNode" slot="editNode" />
<slot name="delNode" slot="delNode" />
</item>
</div>
</div>
@@ -83,9 +87,10 @@
<script>
import { Tree, TreeNode } from './Tree.js'
import $ from 'jquery'
import { addHandler, removeHandler } from './tools.js'
let fromComp = null
let fromComp = ''
export default {
data: function () {
return {
@@ -94,7 +99,7 @@
isDragEnterUp: false,
isDragEnterBottom: false,
isDragEnterNode: false,
expanded: true
expanded: this.defaultExpanded
}
},
props: {
@@ -108,25 +113,46 @@
defaultTreeNodeName: {
type: String,
default: 'New tree node'
},
defaultExpanded: {
type: Boolean,
default: true
}
},
computed: {
itemIconClass () {
return this.model.isLeaf ? 'icon-file' : 'icon-folder'
return this.model.isLeaf ? 'vtl-icon-file' : 'vtl-icon-folder'
},
caretClass () {
return this.expanded ? 'icon-caret-down' : 'icon-caret-right'
return this.expanded ? 'vtl-icon-caret-down' : 'vtl-icon-caret-right'
},
isFolder() {
isFolder () {
return this.model.children &&
this.model.children.length
},
treeNodeClass () {
const {
model: {
dragDisabled,
disabled
},
isDragEnterNode
} = this
return {
'vtl-tree-node': true,
'vtl-active': isDragEnterNode,
'vtl-drag-disabled': dragDisabled,
'vtl-disabled': disabled
}
}
},
mounted () {
const vm = this
$(window).on('keyup', function (e) {
addHandler(window, 'keyup', function (e) {
// click enter
if (e.keyCode === 13 && vm.editable) {
vm.editable = false
@@ -134,24 +160,26 @@
})
},
beforeDestroy () {
$(window).off('keyup')
removeHandler(window, 'keyup')
},
methods: {
updateName (e) {
var oldName = this.model.name;
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 () {
const vm = this
if (window.confirm('Are you sure?')) {
vm.model.remove()
}
var node = this.getRootNode()
node.$emit('delete-node', this.model)
},
setEditable () {
this.editable = true
this.$nextTick(() => {
$(this.$refs.nodeInput).trigger('focus')
this.$refs.nodeInput.focus()
// fireFocusEvent(this.$refs.nodeInput)
})
},
@@ -166,6 +194,7 @@
},
mouseOver(e) {
if (this.model.disabled) return
this.isHover = true
},
@@ -173,18 +202,29 @@
this.isHover = false
},
click() {
var node = this.getRootNode()
node.$emit('click', this.model);
},
addChild(isLeaf) {
const name = isLeaf ? this.defaultLeafNodeName : this.defaultTreeNodeName
this.expanded = true
var node = new TreeNode(name, isLeaf)
var node = new TreeNode({ name, isLeaf })
this.model.addChildren(node, true)
var root = this.getRootNode();
root.$emit('add-node', node)
},
dragStart(e) {
fromComp = this
e.dataTransfer.effectAllowed = 'move'
return true
if (!(this.model.dragDisabled || this.model.disabled)) {
fromComp = this
// for firefox
e.dataTransfer.setData("data","data");
e.dataTransfer.effectAllowed = 'move'
return true
}
return false
},
dragEnd(e) {
fromComp = null
@@ -194,20 +234,21 @@
return true
},
dragEnter(e) {
if (this.model.isLeaf) {
return
}
if (!fromComp) return
if (this.model.isLeaf) return
this.isDragEnterNode = true
},
dragLeave(e) {
this.isDragEnterNode = false
},
drop(e) {
if (!fromComp) return
fromComp.model.moveInto(this.model)
this.isDragEnterNode = false
},
dragEnterUp () {
if (!fromComp) return
this.isDragEnterUp = true
},
dragOverUp (e) {
@@ -215,14 +256,17 @@
return true
},
dragLeaveUp () {
if (!fromComp) return
this.isDragEnterUp = false
},
dropUp () {
if (!fromComp) return
fromComp.model.insertBefore(this.model)
this.isDragEnterUp = false
},
dragEnterBottom () {
if (!fromComp) return
this.isDragEnterBottom = true
},
dragOverBottom (e) {
@@ -230,11 +274,20 @@
return true
},
dragLeaveBottom () {
if (!fromComp) return
this.isDragEnterBottom = false
},
dropBottom () {
if (!fromComp) return
fromComp.model.insertAfter(this.model)
this.isDragEnterBottom = false
},
getRootNode() {
var node = this.$parent
while (node._props.model.name !== 'root') {
node = node.$parent
}
return node;
}
},
beforeCreate () {
@@ -243,7 +296,7 @@
}
</script>
<style lang="less" rel="stylesheet/less" scoped>
<style lang="less" rel="stylesheet/less">
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?ui1hbx');
@@ -255,7 +308,7 @@
font-style: normal;
}
.vue-tree-icon {
.vtl-icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: none;
@@ -268,7 +321,7 @@
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&.item-icon {
&.vtl-menu-icon {
margin-right: 4px;
&:hover {
color: inherit;
@@ -279,52 +332,52 @@
}
}
.icon-file:before {
.vtl-icon-file:before {
content: "\e906";
}
.icon-folder:before {
.vtl-icon-folder:before {
content: "\e907";
}
.icon-caret-down:before {
content: "\e900";
}
.icon-caret-right:before {
.vtl-icon-caret-down:before {
content: "\e901";
}
.icon-edit:before {
.vtl-icon-caret-right:before {
content: "\e900";
}
.vtl-icon-edit:before {
content: "\e902";
}
.icon-folder-plus-e:before {
.vtl-icon-folder-plus-e:before {
content: "\e903";
}
.icon-plus:before {
.vtl-icon-plus:before {
content: "\e904";
}
.icon-trash:before {
.vtl-icon-trash:before {
content: "\e905";
}
.border {
.vtl-border {
height: 5px;
&.up {
&.vtl-up {
margin-top: -5px;
background-color: transparent;
}
&.bottom {
&.vtl-bottom {
background-color: transparent;
}
&.active {
&.vtl-active {
border-bottom: 3px dashed blue;
/*background-color: blue;*/
}
}
.tree-node {
.vtl-tree-node {
display: flex;
align-items: center;
padding: 5px 0 5px 1rem;
.input {
.vtl-input {
border: none;
max-width: 150px;
border-bottom: 1px solid blue;
@@ -332,23 +385,23 @@
&:hover {
background-color: #f0f0f0;
}
&.active {
&.vtl-active {
outline: 2px dashed pink;
}
.caret {
.vtl-caret {
margin-left: -1rem;
}
.operation {
.vtl-operation {
margin-left: 2rem;
letter-spacing: 1px;
}
}
.item {
.vtl-item {
cursor: pointer;
}
.tree-margin {
.vtl-tree-margin {
margin-left: 2em;
}
</style>

View File

@@ -4,4 +4,4 @@
exports.VueTreeList = require('./VueTreeList.vue')
exports.TreeNode = require('./Tree.js').TreeNode
exports.Tree = require('./Tree.js').Tree
exports.Record = require('./Tree.js').Record
// exports.Record = require('./Tree.js').Record

31
src/tools.js Normal file
View File

@@ -0,0 +1,31 @@
/**
* Created by ayou on 18/2/6.
*/
var handlerCache
exports.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
}
}
exports.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
}
}
// exports.fireFocusEvent = function (ele) {
// var event = new FocusEvent()
// ele.dispatch(event)
// }