Omi樹組件omi-tree編寫指南

Omi框架可以以少許的代碼聲明式地編寫可拖拽移動節點的樹形組件。
一般樹組件可以考驗UI框架的健壯性,由於須要使用到UI框架的以下特性:html

  • 組件嵌套
  • 組件傳值
  • 組件批量傳值
  • 組件依賴自身遞歸嵌套(nest-self)
  • 子、孫或炎黃子孫訪問根組件實例

下面來介紹下使用 omi-tree 的開發全過程。你也能夠無視文章,先體驗一把和直接編輯源碼試一把:node

omi-tree playgroundgit

類劃分

  • tree.js 樹組件的根容器類,包含節點移動,根據id獲取節點等通用方法,這裏把其排除在tree-node以外
  • tree-node.js 樹節點,自遞歸嵌套組件,由於tree-node中能夠包含tree-node

樹的數據規則:github

{
    name: 'Root',
    children: [
        {
            name: 'A',
            id: 1,
            children: [
                { id: 4, name: 'A1', children: [] },
                { id: 7, name: 'A2', children: [] }
            ]
        },
        {
            name: 'B',
            id: 2,
            children: [
                { id: 5, name: 'B1', children: [] },
                { id: 8, name: 'B2', children: [] }
            ]
        },
        {
            name: 'C',
            id: 3, children: [
            { id: 6, name: 'C1', children: [] },
            { id: 9, name: 'C2', children: [] }
        ]
        }
    ]
}

能夠看到,每一個節點都有惟一的id來標識,每一個節點也有children屬性來存放本身的子節點的信息。web

組件HTML結構

tree結構:框架

<ul>
  <tree-node o-repeat="child in children" group-data="data.children"></tree-node>
</ul>
  • 經過 o-repeat 生成全部 tree-node
  • group-data 把 data.children 的數據批量傳遞給各個 tree-node

這裏須要特別注意的是:dom

  • o-repeat 等全部指令對應的 scope 數據是 this.data
  • group-data,data等等 的 scope 是 this

tree-node結構:this

<li data-node-id="{{id}}"  draggable="true"  ondragstart="dragStartHandler" ondragleave="dragLeaveHandler"  ondrop="dropHandler" ondragover="dragOverHandler" >
    <div data-node-id="{{id}}">{{name}}</div>
    <ul data-node-id="{{id}}" o-if="children.length > 0">
        <tree-node o-repeat="child in children" group-data="data.children"></tree-node>
    </ul>
</li>

能夠看到每一個tree-node都標記了draggable表明能夠拖拽,drag和drop的支持狀況你們能夠caniuse一把。code

  • 每一個tree-node 既是拖拽對應,也是drop容器對象
  • li、div和ul都標記了 data-node-id 來存放id在dom元素上方便js裏讀取和傳遞

完整代碼解析

先看tree:htm

class Tree extends Omi.Component {
    //移動節點
    moveNode(id, parentId) {
        if (id === parentId) {
            return
        }

        if(this.check(parentId, id)) {
            let parent = this.getChildById(parentId, this.data.children)
            let child = this.removeChildById(id, this.data.children)
            parent.children.push(child)
            this.update()
        }
    }
    //驗證子節點的孩子節點是否包含父親節點,這裏主要是爲了防止把父節點拖拽到本身的孩子節點當中,這是個錯誤的邏輯操做
    check(parentId, childId){
        let current = this.getChildById(childId, this.data.children),
            children = current.children
        for (let i = 0, len = children.length; i < len; i++) {
            let child = children[i]
            if (child.id === parentId) {
                return false
            }

            let errorIds = this.check(parentId, child.id )
            if (!errorIds) {
                return false
            }
        }

        return true
    }
    //根據id移除child節點數據
    removeChildById(id, children) {

        for (let i = 0, len = children.length; i < len; i++) {
            let child = children[i]
            if (child.id === id) {
                children.splice(i, 1)
                return child
            }

            let target = this.removeChildById(id, child.children)
            if (target) {
                return target
            }

        }
    }
    //根據id獲取child節點數據
    getChildById(id, children) {
        for (let i = 0, len = children.length; i < len; i++) {
            let child = children[i]
            if (child.id === id) {
                return child
            }

            let target = this.getChildById(id, child.children)
            if (target) {
                return target
            }
        }
    }

    render() {
        return `<ul>
                  <tree-node o-repeat="child in children" group-data="data.children"></tree-node>
              </ul>`
    }
}

//生成標籤用於聲明式嵌入其餘組件
Omi.tag('tree', Tree)

下面來看 tree-node:

class TreeNode extends Omi.Component {

    dropHandler(evt) {
        //經過evt.dataTransfer.getData接收傳遞過來的數據
        this.getRootInstance(this.parent).moveNode(parseInt(evt.dataTransfer.getData("node-id")), parseInt(evt.target.dataset['nodeId']))
        this.node && this.node.classList.remove('drag-over')
        evt.stopPropagation()
        evt.preventDefault()

    }

    getRootInstance(parent){
        if(parent.moveNode){
            return parent
        }else{
            return this.getRootInstance(parent.parent)
        }

    }

    dragOverHandler(evt){
        this.node.classList.add('drag-over')
        evt.stopPropagation()
        evt.preventDefault()
    }

    dragLeaveHandler(){
        this.node.classList.remove('drag-over')
    }
    
    dragStartHandler(evt){
        //設置要傳遞的數據
        evt.dataTransfer.setData("node-id",this.data.id)
        evt.stopPropagation()
    }
    
    //局部樣式,drag-over是拖拽在node之上的一個激活樣式
    style(){
        return `
            .drag-over{
                border:1px dashed black;
            }
        `
    }

    render(){
        return `
                <li data-node-id="{{id}}"  draggable="true"  ondragstart="dragStartHandler" ondragleave="dragLeaveHandler"  ondrop="dropHandler" ondragover="dragOverHandler" >
                    <div data-node-id="{{id}}">{{name}}</div>
                    <ul data-node-id="{{id}}" o-if="children.length > 0">
                        <tree-node o-repeat="child in children" group-data="data.children"></tree-node>
                    </ul>
                </li>
            `
    }
}

//生成標籤用於聲明式嵌入其餘組件
Omi.tag('tree-node',TreeNode)
  • dragStart的時候經過evt.dataTransfer.setData設置須要傳遞的數據,這裏存放了拖拽的節點id
  • drop的時候經過evt.dataTransfer.getData讀取傳遞過來的數據,這裏取drag的node的節點id
  • 經過 o-if="children.length > 0" 決定是否生成 ul 標籤
  • getRootInstance組件是遞歸去調取tree的對象的實例(由於tree-node可能包含tree-node,因此須要遞歸讀parent)
  • 拿到tree的實例以後,調用tree的對象的實例的moveNode方法去移動節點,moveNode的本質就是修改節點數據,而後update組件

到此位置,複雜的拖拽移動都完成了。增刪改查就更加簡單了,你們能夠接着試試~~~

Omi相關

相關文章
相關標籤/搜索