Coder Social home page Coder Social logo

shuirong / vue-drag-tree Goto Github PK

View Code? Open in Web Editor NEW
389.0 10.0 94.0 3.51 MB

🌴🌳a Vue's drag and drop tree component || 🌾Demo

Home Page: https://vigilant-curran-d6fec6.netlify.com/#/

License: Other

Vue 44.62% JavaScript 55.38%
vue vue-components npm-package tree vue-drag-tree drag drop

vue-drag-tree's Introduction

vue-drag-tree

VersionDownloadsLicense

It's a tree components(Vue2.x) that allow you to drag and drop the node to exchange their data .

Feature

  • Double click on an node to turn it into a folder
  • Drag and Drop the tree node, even between two different levels
  • Customize your node (how to display node. eg: node name and left icon )
  • Controls whether a particular node can be dragged and whether the node can be plugged into other nodes
  • Append/Remove Node in any level (#TODO)

中文 || Please Star! if it's helpful. Example Project

Preview


demo

Getting Start


Install

npm install vue-drag-tree --S

or

yarn add vue-drag-tree -S

Usage

the following code is come from here

P.S. If you get error about Vue packages version mismatch

// Update the version of Vue and vue-template-compiler to latest is fine.  
npm install vue@latest -S
npm install vue-template-compiler@latest -D

main.js

import Vue from 'vue'
import VueDragTree from 'vue-drag-tree'
import 'vue-drag-tree/dist/vue-drag-tree.min.css'

Vue.use(VueDragTree)

test.vue

<template>
	<vue-drag-tree :data='data' :allowDrag='allowDrag' :allowDrop='allowDrop' :defaultText='"New Node"' @current-node-clicked='curNodeClicked' @drag="dragHandler" @drag-enter="dragEnterHandler" @drag-leave="dragLeaveHandler" @drag-over="dragOverHandler" @drag-end="dragEndHandler" @drop="dropHandler" v-slot="slotProps">
    <!-- customize your node here if don't like the default / 如果你不喜欢默认样式,可以在这里定制你自己的节点 -->
    <span :class="[slotProps.isClicked ? 'i-am-clicked' : 'i-am-not-clicked']"></span>
    <span class='i-am-node-name'>{{slotProps.nodeName}}</span>
    </vue-drag-tree>
</template>
<script>
export default{
  data(){
    return{
      data: [
        {
          name: 'Node 0-0',
          id: 0,
          children: [
            {
              name: 'Node 1-1',
              id: 3,
              children: [
                {
                  name: 'Node 2-1',
                  id: 4,
                  children: []
                },
                {
                  name: 'Node 2-2',
                  id: 10,
                  children: []
                }
              ]
            },
            {
              name: 'Node 1-2',
              id: 13,
              children: []
            }
          ]
        },
        {
          name: 'Node 0-1',
          id: 14,
          children: []
        }
      ]
    }
  },
  methods: {
   	allowDrag(model, component) {
      if (model.name === 'Node 0-1') {
        // can't be dragged
        return false;
      }
      // can be dragged
      return true;
    },
    allowDrop(model, component) {
      if (model.name === 'Node 2-2') {
        // can't be placed
        return false;
      }
      // can be placed
      return true;
    },
    curNodeClicked(model, component) {
      // console.log('curNodeClicked', model, component);
    },
    dragHandler(model, component, e) {
      // console.log('dragHandler: ', model, component, e);
    },
    dragEnterHandler(model, component, e) {
      // console.log('dragEnterHandler: ', model, component, e);
    },
    dragLeaveHandler(model, component, e) {
      // console.log('dragLeaveHandler: ', model, component, e);
    },
    dragOverHandler(model, component, e) {
      // console.log('dragOverHandler: ', model, component, e);
    },
    dragEndHandler(model, component, e) {
      // console.log('dragEndHandler: ', model, component, e);
    },
    dropHandler(model, component, e) {
      // console.log('dropHandler: ', model, component, e);
    }
  }
}
<script>

API


Attributes

Name Description Type Default
data data of the tree Array --
defaultText default text of new node String "New Node"
allowDrag Judging which node can be dragged Function ()=>true
allowDrop Judging which node can be plugged into other nodes Function ()=>true
disableDBClick disable append a new child node by double click node Boolean false

Method

Name Description arguments
current-node-clicked Tell you which node was clicked (model,component) model: node data was clicked. component: VNode data for the node was clicked
drag The drag event is fired every few hundred milliseconds as an node is being dragged by the user (model,component,e) model: node data was dragged. component: VNode data for the node was dragged; e: drag event
drag-enter The drag-enter event is fired when a dragged node enters a valid drop target (model,component,e) model: data of the valid drop target; component: VNode of the valid drop target; e: drag event
drag-leave The drag-leave event is fired when a dragged node leaves a valid drop target (model,component,e) model: data of the valid drop target; component: VNode of the valid drop target; e: drag event
drag-over The drag-over event is fired when an node is being dragged over a valid drop target (model,component,e) model: data of the valid drop target; component: VNode of the valid drop target; e: drag event
drag-end The drag-end event is fired when a drag operation is being ended (model,component,e) model: node data was dragged. component: VNode data for the node was dragged; e: drag event
drop The drop event is fired when an node is dropped on a valid drop target. (model,component,e) model: data of the valid drop target; component: VNode of the valid drop target; e: drag event

Slot

<vue-drag-tree ... v-slot="slotProps">
    <!-- customize your node here if don't like the default -->
    <span :class="[slotProps.isClicked ? 'i-am-clicked' : 'i-am-not-clicked']"></span>
    <span class='i-am-node-name'>{{slotProps.nodeName}}</span>
</vue-drag-tree>

slotPropshas two attributes:

attribute name description value type
nodeName the name of displaying node String
isClicked if the node is clicked (true means expanded) Boolean

License


The 996ICU License (996ICU)

vue-drag-tree's People

Contributors

astatroth avatar betterworld-liuser avatar dkhjmu avatar droooney avatar pavelblossom avatar shuirong avatar simon-budin-tcy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vue-drag-tree's Issues

Update Documentation

Hi i'm using your lib, that is light and easy.
I suggest you to update the documentation: the event emitted to check the current node clicked is @current-node-clicked and not @current-clicked.

I suggest you also and allowNewItem to enable/disable the double-click adding of you Item.

Bye

Drag and Drop to External Container?

How does one go about dragging a tree node to an external container? The use case would be dragging tree nodes into a list inside an adjacent card. In trying to do this, the @drop event does not seem to fire unless dropped into the tree itself.

Error in wiki how use need fix it and

now is <vue-darg-tree :model='data' :current-highlight='true' :default-text='"New A Girl"' :hover-color='"lightblue"' :highlight-color='green'>

need change to

and when i try use this

i have next error [Vue warn]: Failed to mount component: template or render function not defined.

vue.runtime.esm.js:472 [Vue warn]: Failed to mount component: template or render function not defined.

found in

--->
at node_modules/vue-drag-tree/src/vue-drag-tree.vue

展开的子节点能不能加个max-height

我这边的场景是数据量比较多 然后拖动的时候就尴尬了 太长了没法拖上去,添加个max-height然后overflow: scroll; 最好是能把这个高度暴露成组件属性

Multiple errors of function / property undefined

Hello, i have some errors when i try to use the tree, i use your demo code without modifying anything.

And i can grab something but can't drop.

On Vue v2.9.1

capture d ecran 2017-11-14 a 14 47 49

    <vue-drag-tree :model='data' :current-highlight='true' :default-text='"New A Girl"' :hover-color='"lightblue"' :highlight-color='"green"'></vue-drag-tree>
</template>

<script>
    import VueDragTree from './VueDragTree'
    import Vue from 'vue'

    export default {
        name: 'Test',
        components: {'vue-drag-tree' : VueDragTree},
        data() {
            return {
                data: {
                    name: 'Root',
                    id: 0,
                    children: [{
                        name: 'Node 1-1',
                        id: 1,
                        children: [{
                            name: 'Node 2-1',
                            id: 2
                        }]
                    },
                    {
                        name: 'Node 1-2',
                        id: 3
                    }]
                }
            }
        },
        methods: {
            assignData(data) {
                this.data = data
            },
            curNodeClicked(model,component) {
            // information of the node clicked just now.
            },
        }
    }
</script>
    <div>
        <div :id='model.id' @click="toggle" @dblclick="changeType" draggable='true' @dragstart='dragStart' @dragover='dragOver' @dragenter='dragEnter' @dragleave='dragLeave' @drop='drop' @dragend='dragEnd' class='treeNodeText' @mouseover='mouseOver' @mouseout='mouseOut' :style='styleObj'>
            <span :class="[isClicked ? 'nodeClicked' : '','vue-drag-node-icon']"></span>
            {{model.name}}
            <span @click="removeChild(model.id)" v-if='model.id !="0"'>&nbsp;x</span>
        </div>
        <div class='treeMargin' v-show="open" v-if="isFolder">
            <item v-for="model in model.children" :model="model" :key='model.id' :current-highlight='currentHighlight' :default-text='defaultText'  :hover-color='hoverColor' :highlight-color='highlightColor'>
            </item>
            <div class='changeTree' @click="addChild" @drop='dropPlus' @dragover='dragOverPlus' @dragenter='dragEnterPlus'>+</div>
        </div>
    </div>
</template>

<script>
let id = 1000
let fromData = ''
let toData = ''
let fromParentModelChildren = ''
let nodeClicked = undefined
export default {
    name: 'VueDragTree',
    data: function () {
        return {
            open: false,
            isClicked: false,
            styleObj: {
                background: 'white',
            }
        }
    },
    props: {
        model: Object,
        'default-text': String,
        'current-highlight': Boolean,
        'hover-color': String,
        'highlight-color': String,
    },
    computed: {
        isFolder() {
            return this.model.children &&
                this.model.children.length
        },
    },
    methods: {
        toggle() {
            if (this.isFolder) {
                this.open = !this.open
            }

            let rootTree = this.findRoot()
            rootTree.$parent.curNodeClicked(this.model, this)
            this.isClicked = !this.isClicked

            if (this.currentHighlight) {

                if (nodeClicked != this.model.id) {
                    let treeParent = rootTree.$parent

                    let nodeStack = [treeParent.$children[0]]
                    while (nodeStack.length != 0) {
                        let item = nodeStack.shift()
                        item.styleObj.background = 'white'
                        if (item.$children && item.$children.length > 0) {
                            nodeStack = nodeStack.concat(item.$children)
                        }
                    }

                    this.styleObj.background = this.highlightColor ? this.highlightColor : '#99A9BF'
                    nodeClicked = this.model.id
                }
            }
        },
        exchangeData(rootCom, from, to) {
            //如果drag的目的地是 + - 符号的话,退出。
            if (!to || !from || typeof to == 'string' || from.id == to.id) {
                return
            }
            from = JSON.parse(JSON.stringify(from))
            to = JSON.parse(JSON.stringify(to))
            // copy一个,最后再赋值给state.treeData.这样能保证值的变化都会触发视图刷新(因为JS判断引用类型是否相同是根据内存地址.)
            let treeData = JSON.parse(JSON.stringify(this.model))
            let nodeStack = [treeData]
            let status = 0
            // 如果from或者to节点存在父子/祖辈关系,会报id of undefined的错。这种情况不考虑拖拽功能,所以catch住,返回/return就行
            try {
                // 广度优先遍历,找到涉及数据交换的两个对象.然后交换数据.
                while (!(status === 2)) {
                    let item = nodeStack.shift()
                    if (item.id == from.id) {
                        item.id = to.id
                        item.name = to.name
                        if (to.children && to.children.length > 0) {
                            item['children'] = to.children
                        } else {
                            item.children = []
                        }
                        status++
                        //找到后,跳出当前循环.
                        continue;
                    }
                    if (item.id == to.id) {
                        item.id = from.id
                        item.name = from.name
                        if (from.children && from.children.length > 0) {
                            item['children'] = from.children
                        } else {
                            item.children = []
                        }
                        status++
                        //找到后,跳出当前循环.
                        continue;
                    }
                    if (item.children && item.children.length > 0) {
                        nodeStack = nodeStack.concat(item.children)
                    }
                }
            } catch (e) {
                return
            }
            //API: 对外开放交换后的数据的赋值操作
            rootCom.assignData(treeData)
        },
        changeType() {
            // 用户需要高亮-->才纪录当前被点击节点
            if (this.currentHighlight) {
                nodeClicked = this.model.id
            }
            if (!this.isFolder) {
                this.$set(this.model, 'children', [])
                this.addChild()
                this.open = true
                this.isClicked = true
            }
        },
        mouseOver(e) {
            if ((this.styleObj.background != '#99A9BF' || this.styleObj.background != this.hightlightColor) && e.target.className === 'treeNodeText') {
                e.target.style.background = this.hoverColor ? this.hoverColor : '#E5E9F2'
            }
        },
        mouseOut(e) {
            if ((this.styleObj.background != '#99A9BF' || this.styleObj.background != this.hightlightColor) && e.target.className === 'treeNodeText') {
                e.target.style.background = 'white'
            }
        },
        findRoot() {
            // 返回Tree的根,即递归Tree时的最顶层那个vue-drag-tree组件
            let ok = false
            let that = this
            while (!ok) {
                // 如果父组件有data属性,说明当前组件是Tree组件递归调用发生时的第一个组件。
                // Warning: 因为是判断data属性是否存在,所有在别人使用该组件时,属性名必须得是data
                // v1.0.9-update: add another judgement method.
                if (!/VueDragTreeCom42/.test(that.$parent.$vnode.tag) || that.$parent.data) {
                    ok = true
                    // 交换两者的数据 
                    break
                }
                that = that.$parent
            }
            return that
        },
        addChild() {
            this.model.children.push({
                name: this.defaultText ? this.defaultText : 'New Node',
                id: id++
            })
        },
        removeChild(id) {
            // 获取父组件的model.children
            let parent_model_children = this.$parent.model.children
            // 在父组件model.children里删除
            for (let index in parent_model_children) {
                // 找到该删的id
                if (parent_model_children[index].id == id) {
                    parent_model_children = parent_model_children.splice(index, 1)
                    break
                }
            }
        },
        dragStart(e) {
            // fromData = this.model
            e.dataTransfer.effectAllowed = "move";
            e.dataTransfer.setData("nottext", e.target.innerHTML);
            return true
        },
        dragEnd(e) {
            fromData = undefined
            toData = undefined
            fromParentModelChildren = undefined
        },
        dragOver(e) {
            e.preventDefault()
            return true
        },
        dragOverPlus(e) {
            e.preventDefault()
        },
        dragEnterPlus(e) {
        },
        dragEnter(e) {
            toData = this.model
            let rootTree = this.findRoot()
            rootTree.exchangeData(rootTree.$parent, fromData, toData)
        },
        dragLeave(e) {
            fromData = this.model
            fromParentModelChildren = this.$parent.model.children
            // e.target.style.background = '#7B1FA2'
        },
        drop(e) {
            // toData = this.model
            // e.target.style.background = '#7B1FA2'
        },
        dropPlus(e) {
            // 把from节点插入到当前层级节点的最后一个
            if (this.model.hasOwnProperty('children')) {
                this.model.children.push(fromData)
            } else {
                this.model.children = [fromData]
            }
            // 把from节点从之前的节点删除
            for (let i in fromParentModelChildren) {
                if (fromParentModelChildren[i].id == fromData.id) {
                    fromParentModelChildren.splice(i, 1)
                }
            }
        }
    },
    beforeCreate() {
        this.$options.components.item = require('./VueDragTree')
    },
    created() {
        // console.log('this.hig', this.highlightColor, '|', this.hoverColor)
    },
}
</script>

<style>
.item {
    cursor: pointer;
}
.bold {
    font-weight: bold;
}
.treeNodeText {
    margin: 2px;
    padding: 0.2rem 0.5rem;
    width: fit-content;
    background: #F9FAFC;
    font-size: 18px;
    color: #324057;
}
.treeMargin {
    margin-left: 2rem;
}
.changeTree {
    width: 1rem;
    color: #324057;
}
.vue-drag-node-icon {
    display: inline-block;
    width: 0;
    height: 0;
    padding-right: 3px;
    border-left: 6px solid black;
    border-top: 6px solid transparent;
    border-bottom: 6px solid transparent;
    border-right: 0 solid yellow;
    transition: transform .3s ease-in-out;
}
.nodeClicked {
    transform: rotate(90deg);
}
</style>

点击Tree上的按钮报错

你好, 按照你的说的方式,调用了这个组件 但是 点击Root 或者+ 号时 报错
(点击Root)
Uncaught TypeError: Cannot set property 'background' of undefined
at VueComponent.toggle (vue-drag-tree.vue?db22:70)
at boundFn (vue.esm.js?efeb:186)
at invoker (vue.esm.js?efeb:1943)
at HTMLDivElement.fn._withTask.fn._withTask (vue.esm.js?efeb:1778)
(点击+号)
[Vue warn]: Failed to mount component: template or render function not defined.

found in

--->
at node_modules\vue-drag-tree\src\vue-drag-tree.vue
at src\components\Hello.vue
at src\App.vue

warn @ vue.esm.js?efeb:571

我vue-cli 脚手架的搭建是 npm init webpack-simple xxxx
npm install
然后就按照说明使用组件,报错了
我下了一个你的demo(vue-drag-tree-demo)工程 运行时就没问题,能受累帮忙看看怎么回事么?
main.js

import Vue from 'vue'
import App from './App.vue'

import router from './router'
import TreeView from 'vue-json-tree-view'
import VueDragTree from 'vue-drag-tree'
Vue.component('vue-drag-tree', VueDragTree)
Vue.config.productionTip = false
Vue.use(TreeView)
new Vue({
// el: '#app',
// router,
// render: h => h(App)
el: '#app',
router,
template: '',
components: {
App
}
})

App.vue 跟你的demo工程是一样的

Multiple trees?

Hi,

Just found this lib and it looks good!

Does it or could it support multiple trees, and drag and drop to and from each?

Thanks

Help friend

I need to be able to put an icon on documents not nodes. I get to put it on the first span but not the next.
I also have the need to block the drag, how is it possible

<item> is html5 element?

image

I don't know what label element < item > is. HTML 5 has a < menuItem > label, but it doesn't look like the same thing.
Could you tell me how to use the element??

Node icon and Title

Customize your node (how to display node. eg: node name and left icon ).

  1. May I ask how to customize the left icon?
  2. If I'd like to change the node text to a input that allows user to input the new name, is that possible?

findRoot不太合理

比如,下面这样的话返回的是box组件

<template>
<div>
    <box>
      <vue-drag-tree :model='data' :current-highlight='true' :default-text='"New A Girl"' hover-color='lightblue' highlight-color='green'></vue-drag-tree>
    </box>
  </div>
<template>
<script>
export default {
....
    data () {
      return {
        data: {
....
</script>

Issue with tag item

I am getting this error when trying to use vue-drag-tree. In the template of the vue-drag-tree component I see a tag "item". Not sure what tag that is. I don't see that it is a custom component tag, nor is it a valid html tag. Please clarify. Thanks!

vue.esm.js?efeb:578 [Vue warn]: Failed to mount component: template or render function not defined.

found in

<Item>
<VueDragTreeCom42> at node_modules/vue-drag-tree/src/vue-drag-tree.vue

关于孙辈节点的Bug

有个Bug,当to节点是from节点的孙辈的时候,也会移动过去,然后整个节点都没了,我稍微改进了一下hasInclude的代码。麻烦添加一下,另外我也pull request了另一个特性麻烦检查一下。

const hasInclude = (from, to) => {
  while(from.$parent!=undefined){
    if(from.$parent._uid === to._uid){
      return true
    }else{
      from = from.$parent
    }
  }
  return false
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.