2019年7月8日 星期一

用 emit 取代 Vue.js 的 treeview 事件

用emit取代Vue.js的treeview事件

前言

  在之前的在Vue.js的treeview增加drag功能裡的事件是透過 props 來傳達,但之後又發現有 emit 可以用,最後決定用 emit 取代目前的 props 傳事件,在此做個紀錄。

內容

  這次的程式碼會整合在Vue.js的treeview增加drag功能初探Vue.js的emit,整合的程式碼如下
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id='app'>
  <root_treenodeview :rootnode_list='nodelist' @evt-drag-node="onDragNode" @evt-drop-node="onDropNode" @evt-drop-node-parent="onDropNodeParent"></root_treenodeview>
</div>
</body>
</html>

JavaScript 的部分
Vue.component('treenodeview',{
  props : ['node'],
  template : [
    '<ul>\n',
    '  <li>\n',
    '    <div @click="onClick" draggable="true" @dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop">\n',
    '      {{node.name}}\n',
    '      <span v-show="isFolder">[{{isExpand?"-":"+"}}]</span>\n',
    '    </div>\n',
    '  </li>\n',
    '  <div style="height:10px;" @dragenter="onDragEnterParent" @dragover="onDragOverParent" @dragleave="onDragLeaveParent" @drop="onDropParent">\n',
    '    <hr v-show="isShowDragParent" style="margin:0px;">\n',
    '  </div>\n',
    '  <treenodeview v-show="isExpand" v-for="childNode in node.child" :node="childNode" @evt-drag-node="onDragNode" @evt-drop-node="onDropNode" @evt-drop-node-parent="onDropNodeParent"></treenodeview>\n',
    '</ul>\n'
  ].join(''),
  data : function(){
    return {
      isExpand : false,
      isShowDragParent : false,
    };
  },
  computed:{
    isFolder:function(){
      return this.node.child &&
              this.node.child.length;
    }
  },
  methods:{
    onClick : function(){
      if(this.isFolder){
        this.isExpand=!this.isExpand;
      }
    },
    onDragStart : function(evt){
      this.$emit('evt-drag-node', this.node);
    },
    onDragOver : function(evt){
      evt.preventDefault();
    },
    onDrop : function(evt){
      evt.preventDefault();
      this.$emit('evt-drop-node', this.node);
    },
    onDragEnterParent : function(evt){
      this.isShowDragParent = true;
    },
    onDragOverParent : function(evt){
      evt.preventDefault();
      this.isShowDragParent = true;
    },
    onDragLeaveParent : function(evt){
      this.isShowDragParent = false;
    },
    onDropParent : function(evt){
      evt.preventDefault();
      this.isShowDragParent = false;
      this.$emit('evt-drop-node-parent', this.node);
    },
    onDragNode : function(node){
      this.$emit('evt-drag-node', node);
    },
    onDropNode : function(node){
      this.$emit('evt-drop-node', node);
    },
    onDropNodeParent : function(node){
      this.$emit('evt-drop-node-parent', node);
    }
  }
});
Vue.component('root_treenodeview',{
  props : ['rootnode_list'],
  template:[
    '<div>\n',
    '  <treenodeview v-for="rootNode in rootnode_list" :node="rootNode" @evt-drag-node="onDragNode" @evt-drop-node="onDropNode" @evt-drop-node-parent="onDropNodeParent"></treenodeview>\n',
    '</div>\n',
  ].join(''),
  methods:{
    onDragNode : function(node){
      this.$emit('evt-drag-node', node);
    },
    onDropNode : function(node){
      this.$emit('evt-drop-node', node);
    },
    onDropNodeParent : function(node){
      this.$emit('evt-drop-node-parent', node);
    }
  }
});
//
function Node(opt){
  this.name = (opt.name) ? opt.name : '';
  this.parent = undefined;
  this.child = [];
}
Node.prototype.setParent = function(parentNode){
  let isFindInParent = false;
  let findNode = parentNode;
  while(findNode){
    if(findNode === this){
      isFindInParent = true;
      break;
    }
    findNode = findNode.parent;
  }
  if(isFindInParent)
    return;
  
  if(this.parent){
    //
    if(isFindInParent)
      return;
    let index=-1;
    for(let i=0;i<this.parent.child.length;i++){
      if(this.parent.child[i] === this){
        index = i;
        break;
      }
    }
    //
    if(index >= 0){
      this.parent.child.splice(index,1);
    }
  }
  //
  this.parent = parentNode;
  if(parentNode)
    parentNode.child.push(this);
};
let nodeRoot=new Node({name:"root"});
let node1=new Node({name:"node1"});
let node2=new Node({name:"node2"});
node1.setParent(nodeRoot);
node2.setParent(node1);

let app = new Vue({
  el : '#app',
  data:function(){
    return {
      nodelist:[nodeRoot],
      dragNode : null
    }
  },
  methods:{
    onDragNode:function(node){
      this.dragNode = node;
    },
    onDropNode:function(node){
      if(this.dragNode){
        if(this.dragNode.parent === undefined){
          let index=-1;
          for(let i=0;i<this.nodeList.length;i++){
            if(this.nodeList[i] === this.dragNode){
              index = i;
              break;
            }
          }
          //
          if(index>=0){
            this.nodeList.splice(index,1);
          }
        }
        this.dragNode.setParent(node);
      }
    },
    onDropNodeParent:function(node){
      if(this.dragNode && this.dragNode!==node){
        if(node.parent)
          this.dragNode.setParent(node.parent);
        else{
          this.dragNode.setParent(undefined);
          this.nodelist.push(this.dragNode);
        }
      }
    }
  }
});

這次會在 treenodeview 與 root_treenodeview都新增 onDragNode 、 onDropNode 與 onDropNodeParent,實作的部分都只是喚起 emit,主要的原因在 初探Vue.js的emit 裡說過只能傳一層,所以這些 emit 其實是往上再傳。

  這次的做法有比較好嗎?只是比較接近"官方"用法而已,如果有看過官方寫的 treeview(Tree View Example),裡面的事件傳遞都是用 emit 的方式,而不是用 props 傳事件,所以改成"官方"的用法。

參考資料

Tree View Example

相關文章

在Vue.js的treeview增加drag功能
初探Vue.js的emit

沒有留言:

張貼留言