惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Exploit Database - CXSecurity.com
N
News and Events Feed by Topic
Latest news
Latest news
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
CXSECURITY Database RSS Feed - CXSecurity.com
IT之家
IT之家
V
V2EX
WordPress大学
WordPress大学
Apple Machine Learning Research
Apple Machine Learning Research
Cisco Talos Blog
Cisco Talos Blog
K
Kaspersky official blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
S
SegmentFault 最新的问题
小众软件
小众软件
A
Arctic Wolf
酷 壳 – CoolShell
酷 壳 – CoolShell
腾讯CDC
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
G
GRAHAM CLULEY
罗磊的独立博客
T
Tor Project blog
C
Cisco Blogs
美团技术团队
博客园 - Franky
月光博客
月光博客
博客园 - 三生石上(FineUI控件)
T
Threat Research - Cisco Blogs
Cyberwarzone
Cyberwarzone
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
有赞技术团队
有赞技术团队
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Security Latest
Security Latest
博客园 - 司徒正美
Hugging Face - Blog
Hugging Face - Blog
Spread Privacy
Spread Privacy
J
Java Code Geeks
C
CERT Recently Published Vulnerability Notes
大猫的无限游戏
大猫的无限游戏
S
Securelist
The Cloudflare Blog
博客园 - 叶小钗
D
Darknet – Hacking Tools, Hacker News & Cyber Security
阮一峰的网络日志
阮一峰的网络日志
雷峰网
雷峰网
Project Zero
Project Zero

博客园 - 【当耐特】

苹果 icloud 把我 ipad min 所有照片丢失 Wechart 饼图 - 【当耐特】 - 博客园 【开源】Skatch 正式发布 - 极速渲染抽象派草图 - 【当耐特】 【开源】小程序、小游戏和Web运动引擎 to2to 发布 - 【当耐特】 【开源】微信小程序、小游戏以及 Web 通用 Canvas 渲染引擎 - Cax - 【当耐特】 【开源】Qone 正式发布,使 javascript 支持 .NET LINQ - 【当耐特】 你不容错过的 腾讯 AlloyTeam Web 前端大会 看点完全剖析 腾讯 AlloyCrop 1.0 发布 - 【当耐特】 50行代码实现的高性能动画定时器 raf-interval - 【当耐特】 - 博客园 QQ日迹Omi实战开发,从0到1 - 【当耐特】 - 博客园 腾讯AlloyTeam正式发布pasition - 制作酷炫Path过渡动画 - 【当耐特】 腾讯AlloyTeam正式发布omi-cli脚手架 - 创建网站无需任何配置 - 【当耐特】 腾讯AlloyTeam正式发布Canvas魔幻线条 - curvejs - 【当耐特】 Omi官方插件系列 - omi-transform介绍 - 【当耐特】 Omi新成员omi-router正式发布 - 【当耐特】 - 博客园 Omi架构与React Fiber - 【当耐特】 - 博客园 Omi框架Store体系的前世今生 - 【当耐特】 - 博客园 omi-cli新版发布-升级webpack2和支持sass生成组件局部CSS - 【当耐特】 - 博客园 Omi应用md2site-0.5.0发布-支持动态markdown拉取解析 - 【当耐特】 - 博客园
Omi树组件omi-tree编写指南 - 【当耐特】 - 博客园
【当耐特】 · 2017-05-02 · via 博客园 - 【当耐特】

2017-05-02 14:53  【当耐特】  阅读(1436)  评论()    收藏  举报

Omi框架能够以少量的代码声明式地编写可拖拽移动节点的树形组件。
通常树组件能够考验UI框架的健壮性,因为需要使用到UI框架的如下特性:

  • 组件嵌套
  • 组件传值
  • 组件批量传值
  • 组件依赖自身递归嵌套(nest-self)
  • 子、孙或炎黄子孙访问根组件实例

下面来介绍下使用 omi-tree 的开发全过程。你也可以无视文章,先体验一把和直接编辑源码试一把:

omi-tree playground

类划分

  • tree.js 树组件的根容器类,包含节点移动,根据id获取节点等通用方法,这里把其排除在tree-node之外
  • tree-node.js 树节点,自递归嵌套组件,因为tree-node中可以包含tree-node

树的数据规则:

{
    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属性来存放自己的子节点的信息。

组件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

这里需要特别注意的是:

  • o-repeat 等所有指令对应的 scope 数据是 this.data
  • group-data,data等等 的 scope 是 this

tree-node结构:

<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一把。

  • 每个tree-node 既是拖拽对应,也是drop容器对象
  • li、div和ul都标记了 data-node-id 来存放id在dom元素上方便js里读取和传递

完整代码解析

先看tree:

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相关