一款轻量级树形控件EasyTreeview

使用方法

引入

<link rel="stylesheet" type="text/css" href="./css/index.min.css">
<div id="tree"></div>
<script type="text/javascript" src="./js/index.min.js"></script>
<script type="text/javascript">
	var tv = new EasyTreeview({
		el: 'tree',
		data: [
			{
				text: 'root',
				children: [
					{
						text: 'child - 1'
					}, {
						text: 'child - 2',
						children: [
							{
								text: 'child - 2 - 1'
							}
						]
					}
				]
			}
		]
	})
</script>

完整示例

var tv = new EasyTreeview({
	el: 'tree',
	draggable: true,
	checkable: true,
	onClick: function (symbol, node) {
		// console.log('%o %o', symbol, node)
	},
	onDragged: function (symbol, node) {
		// console.log('%o %o', symbol, node)
	},
	onDropped: function (symbol, node) {
		// console.log('%o %o', symbol, node)
	},
	onChecked: function (symbol, node, symbols) {
		// console.log('%o %o %o', symbol, node, symbols)
	},
	onUnchecked: function (symbol, node, symbols) {
		// console.log('%o %o %o', symbol, node, symbols)
	},
	data: [
		{
			id: 0,
			text: '服装',
			children: [
				{
					id: 4,
					text: '男装'
				}, {
					id: 6,
					text: '女装',
					style: {
						color: '#e33',
						lineHeight: '21px'
					},
					children: [
						{
							id: 12,
							text: '裤子',
							style: {
								color: '#fff',
								backgroundColor: '#bbdefb'
							}
						}, {
							id: 13,
							text: '裙子',
							checked: true,
							on: {
								click: function (e) {
									console.log(e.target)
								}
							}
						}
					]
				}
			]
		}
	]
})

可配置项

el (String | required)

DOM 已有节点,用以作为 树形控件的容器。如果该节点不存在,控件将不会被创建。

draggable (Boolean | default: false)

如果值为 true, 树节点将可被拖拽。

checkable (Boolean | default: false)

如果值为 true, 树节点将包含复选框且可被选中。

data (Array | required)

用以创建树结构的用户数据。

data[i].text (String | required)

树节点的文本内容。

data[i].style (Object | optional)

自定义的节点样式。

data[i].on (Object | optional)

自定义的节点事件监听。

data[i].children (Object | optional)

当前节点的下属子树。

可配置的事件监听

onClick (Function | default: f (symbol, node) {})

  • symbol, 系统分配的序列号
  • node, 根据节点数据打包的系统对象

当树节点被点击时触发。

onDragged (Function | default: f (symbol, node) {})

当树节点被拖拽时触发。 (树控件 draggable 应为 true).

onDropped (Function | default: f (symbol, node) {})

当被拖拽节点被丢到该节点时触发。 (树控件 draggable 应为 true).

PS: 参数 node 不是被拖拽节点,而是被丢上的节点。

onChecked (Function | default: f (symbol, node, symbols) {})

  • symbols, 所有已选中的节点标号

当树节点被选中时触发。 (树控件 checkable 应为 true).

onUnchecked (Function | default: f (symbol, node, symbols) {})

当树节点被取消选中状态时触发。 (树控件 checkable 应为 true).

可调用的方法

getNodes () : Array

获取系统封装的所有节点对象

getCheckedNodes (): Array

获取被选中的系统封装的所有节点对象

getTree (): Array

获取原始数据当前的树形结构。这在拖拽树节点,树形结构发生变化时尤其有用。

其他

风格化

如果需要改变树控件的整体风格样式,这里并不建议在节点数据中挨个配置样式属性,而是希望引用者重写并覆盖树节点当前的类样式。

控件的 DOM 结构如下:

<ul class="sub-tree">
    <li class="branch-node" draggable="true">
        <div class="node-body">
            <span class="collapse-switch"></span>
            <span class="checkbox"></span>
            <span class="node-text">节点</span>
        </div>
        <ul class="sub-tree">
            <!-- recurse -->
        </ul>
    </li>
</ul>

树 or 森林

有些时候,开发者也许并不清楚自己创建到底是一颗树,还是一个森林。一般情况下,这两者的区别对业务并没有任何影响,因此也无需做差异化处理。然而在这里,如果你创建的是一个森林,那么请不要轻易改变根节点的性质,因为当你将它变为一截枝干时,它便无法再长成为一颗树了。

DOM 结构的创建

在树控件的处理中,循环和递归结构必不可少,递归用以纵向处理树的深度,循环用以横向处理树的广度。

我们可以使用 ul 标签建立子树增加树深,使用 li 标签建立树的节点以增加树宽,这在上文 DOM 结构中可以看到。下面是 dom 创建函数的 简单原型:

var createBranch = function (nodes) {
    // self func, createElement
	var ul = createElement('ul', {
		className: 'sub-tree'
	})
	// loop
	nodes.forEach(function (node) {
		var li = createElement('li', {
			className: 'branch-node'
		})
		if (node.children) {
			// recurse
			li.appendChild(createBranch(node.children))
		}
		ul.appendChild(li)
	})
	return ul
}

在这里,children 起到两个作用:

  1.  作为创建子节点的数据
  2.  初始化时标识节点是否为枝干节点(之后,节点特性由系统负责标识)

状态的切换

在控件创建时,树节点状态(用户可控:checked,collasped,系统记录:branched)是可初始化的;而在用户 选中/不选中 复选框、收缩/展开 子树、拖拽移动树节点 时,对应的 checked、collapsed、branched 状态值 也是需要切换的。虽是同一属性,却有两套业务要处理。面对这种情况,这里将 初始获取属性值和将属性值赋给 DOM 节点 解耦,用户事件刷新属性值和将属性值同步给视图解耦,以实现业务流程最大程度上的松散和可复用。

业务流程:

  1. 创建 DOM 节点时,将节点封装成含有唯一标识 index 的系统对象 node,保存在 节点集合nodeCollection 中
  2. 声明 状态集合 保存特定状态的节点 index,如 checkedSymbol 保存被选中节点的 index
  3. 当初始化和用户事件触发时,更新 状态集合 数据
  4. 使用 状态集合数据 来同步 节点集合 中节点的视图

这样处理,不仅使 代码的可读性和可维护性 更加良好,也解决了功能上的一大痛点:

在循环创建树节点时,如何根据当前节点的 checked 属性同步其父子节点的 checked 属性 (在得到它的 checked 属性时, 它的 父子节点可能尚未创建完毕)。

轻量级

作为一款轻量级树形控件,EasyTreeview 旨在解决常用业务场景,并没有过度拓展,也没有引入任何外部静态资源。代码结构并不复杂,且对其它功能(如动态增删树节点等)也做了可拓展性的支持,如有其它需求,请及时反馈或自行拓展。

源代码

GitHub

码云

最后, 欢迎各路大神前来 Hack,也希望感兴趣者能够加入并成为项目的 contributor。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Google Dart

AngularDart Material Design 输入 顶

Selector: <material-input:not(material-input[multiline])>

14540
来自专栏魂祭心

原 利用Appdomain动态加载程序集,

36980
来自专栏玄魂工作室

如何将HTML字符转换为DOM节点并动态添加到文档中

将字符串动态转换为DOM节点,在开发中经常遇到,尤其在模板引擎中更是不可或缺的技术。 字符串转换为DOM节点本身并不难,本篇文章主要涉及两个主题:<br />

18420
来自专栏HTML5学堂

2016.07 第2周 群问题分享

HTML+CSS 移动端中1px的边框如何实现 2016.07.04~2016.07.08 核心概念: viewport、CSS3属性 参考答案: 一、通过设置...

29860
来自专栏iKcamp

React 深入系列3:Props 和 State

文:徐超,《React进阶之路》作者 授权发布,转载请注明作者及出处 ---- React 深入系列3:Props 和 State React 深...

40360
来自专栏Google Dart

AngularDart4.0 指南- 模板语法二 顶

Class绑定语法类似于属性(property)绑定。 以前缀类开始,可选地跟一个点(.)和一个CSS类的名字替代括号内的元素属性:[class.class-n...

12920
来自专栏web前端教室

WEB前端零基础课-1022本周总结

v-if,根据true或是false,来决定是否插入到页面当中,dom节点,不在页面中

11510
来自专栏一“技”之长

Java开发GUI之列表 原

    awt包中的List控件可以创建一个选择列表,此列表可以支持单选,也可以支持多选。

35220
来自专栏Flutter入门

Vue 绑定简单分析实现

使用js es6 中 Object.defineProperty为我们自己定义的VM创建示例。同时这个方法通过提供了set.get方法的触发我们的监听事件。

15210
来自专栏小古哥的博客园

HTML5复习整理

一、推出的目标 web浏览器兼容性低;文档结构不明确;web应用程序的功能受限 二、语法的改变 内容类型(html或htm);DOCTYPE声明简化;指定字符编...

38670

扫码关注云+社区

领取腾讯云代金券