Omi树组件omi-tree编写指南
Omi框架能够以少量的代码声明式地编写可拖拽移动节点的树形组件。
通常树组件能够考验UI框架的健壮性,因为需要使用到UI框架的如下特性:
- 组件嵌套
- 组件传值
- 组件批量传值
- 组件依赖自身递归嵌套(nest-self)
- 子、孙或炎黄子孙访问根组件实例
下面来介绍下使用 omi-tree 的开发全过程。你也可以无视文章,先体验一把和直接编辑源码试一把:
类划分
- 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相关
- Omi官网omijs.org
- Omi的Github地址https://github.com/AlloyTeam/omi
- 如果想体验一下Omi框架,可以访问 Omi Playground
- 如果想使用Omi框架或者开发完善Omi框架,可以访问 Omi使用文档
- 如果你想获得更佳的阅读体验,可以访问 Docs Website
- 如果你懒得搭建项目脚手架,可以试试 omi-cli
- 如果你有Omi相关的问题可以 New issue
- 如果想更加方便的交流关于Omi的一切可以加入QQ的Omi交流群(256426170)
Omi树组件omi-tree编写指南的更多相关文章
- [学习笔记]最小割树(Gomory-Hu Tree)
最小割树(\(\mathcal{Gomory-Hu Tree}\))简明指南 对于单源最短路径,我们有\(SPFA\)和\(Dijkstra\),对于多源最短路径,我们有\(Floyd\):对于两点间 ...
- Tree( 树) 组件[4]
本节课重点了解 EasyUI 中 Tree(树)组件的使用方法, 这个组件依赖于 Draggable(拖动)和 Droppable(放置)组件.一.方法列表 //部分方法onClick : funct ...
- Tree( 树) 组件[3]
本节课重点了解 EasyUI 中 Tree(树)组件的使用方法, 这个组件依赖于 Draggable(拖动)和 Droppable(放置)组件.一. 事件列表很多事件的回调函数都包含'node'参数, ...
- Tree( 树) 组件[2]
本节课重点了解 EasyUI 中 Tree(树)组件的使用方法, 这个组件依赖于 Draggable(拖动)和 Droppable(放置)组件.一. 异步加载如果想从数据库里获取导航内容, 那么就必须 ...
- Tree( 树) 组件[1]
本节课重点了解 EasyUI 中 Tree(树)组件的使用方法, 这个组件依赖于 Draggable(拖动)和 Droppable(放置)组件. 一. 加载方式//class 加载方式<ul c ...
- Omi教程-组件
组件 Omi框架完全基于组件体系设计,我们希望开发者可以像搭积木一样制作Web程序,一切皆是组件,组件也可以嵌套子组件形成新的组件,新的组件又可以当作子组件嵌套至任意组件形成新的组件... 简单组件 ...
- Omi教程-组件通讯攻略大全
组件通讯 Omi框架组建间的通讯非常遍历灵活,因为有许多可选方案进行通讯: 通过在组件上声明 data-* 传递给子节点 通过在组件上声明 data 传递给子节点 (支持复杂数据类型的映射) 父容器设 ...
- 第二百二十六节,jQuery EasyUI,Tree(树)组件
jQuery EasyUI,Tree(树)组件 本节课重点了解 EasyUI 中 Tree(树)组件的使用方法,这个组件依赖于 Draggable(拖 动)和 Droppable(放置)组件. 一.加 ...
- 『动善时』JMeter基础 — 32、JMeter察看结果树组件详解
目录 1.察看结果树介绍 2.察看结果树界面详解 3.察看结果树的其他功能 (1)将数据写入文件中 (2)Search功能 (3)Scroll automatically选项 4.总结 1.察看结果树 ...
随机推荐
- Winform自定义控件在界面上拖动、滚动鼠标。。会闪烁的解决方法
环境说明: 项目中有一个基类窗体BaseForm,有一个自定义控件TextBoxBase,两个控件都做了一些独特常规的封装和重写,在TextBoxBase中有一点重绘的下划线,发现在窗体运行之后, ...
- 解决此问题:Oracle 删除用户时报 “必须指定 CASCADE 以删除 'SE'”,
这说明你要删除的oracle 用户"SE" 下面还有数据库对象,如 table, view 等,这样你删除用户时必须加选项 cascade:drop user se cascade ...
- 【学习笔记】TCP通信的细节及TCP连接对HTTP事务处理性能影响
从三次握手的细节说起 刚开始尝试使用java等后端语言写IO流,或用套接字(socket)实现简单C/S通信的同学们,常常会接触到的一个概念:就是所谓的"三次握手",socket作 ...
- 【Yii系列】处理请求
缘起 这一章是Yii系列的第三章,前两章给大伙讲解了Yii2.0的安装与Yii2.0的基本框架及基础概念,传送门: [Yii2.0的安装与调试]:http://www.cnblogs.com/rive ...
- 通用数据库帮助类DBHelper(含log日志信息实时记录)
项目需要,需要一个通用的数据库操作类,增删改查.事务.存储过程.日志记录都要有,于是在已有的帮助类上做了一些改进,并将log4j的.NET版--log4net嵌入其中记录sql的执行环境和状态. 用起 ...
- Zookeeper-3.4.9 集群搭建
这里用了三台主机,系统为CentOS7 1.修改hosts #vim /etc/hosts 172.50.0.31 node1 172.50.0.34 node2 172.50.0.37 node3 ...
- 如何用Android Studio查看build.gradle源码
上一篇博客里讲过 build.gradle 里的每一行代码基本都是在调用一个方法,既然是这样,我们就可以用 android studio(下面简称as) 去查看它源码的方法注释说明,这样就可以理解每个 ...
- 大数据时代日志分析平台ELK的搭建
A,首先说说ELK是啥, ELK是ElasticSearch . Logstash 和 Kiabana 三个开源工具组成.Logstash是数据源,ElasticSearch是分析数据的,Kiaba ...
- 【Electron】Electron开发入门(三):main process和web page 通信
一.main process 和 web page 通信 electron框架主进程(Main Process)与嵌入的网页(web page,也就是renderer process)之间的通信 Ma ...
- Xamarin+Prism开发之.net standard化
前面[Visual Studio 2017创建.net standard类库编译出错原因]已经解决.net standard类库的编译问题,现在可以正式进入主题了.作为.net 跨平台开发者也得跟上时 ...