突破编程_前端_JS编程实例(简单树结构组件)

1 开发目标

实现如下简单树结构组件:

在这里插入图片描述

再点击树节点后,会调用客户端传入的回调函数:

在这里插入图片描述

2 详细需求

简单树结构组件需根据客户端提供的参数创建,具备动态构建树形结构节点、选项卡切换及自定义内容显示等功能:

(1)树形结构组件的创建与初始化: 类似于 echarts 等知名商业组件的创建与初始化方式,本组件需要根据客户端提供的参数 container 以及 para 进行创建和初始化。
container 是一个已存在的 DOM 元素(一般是 DIV),组件将在此元素内部构建 TAB 区域,包含选项卡以及 TAB 面板。
para 是本组件的配置参数,该对象应包含以下属性:

{
	"nodes":[
		{
			"id":"1",
			"name":"node_1",
			"children":[
				{
					"id":"11",
					"name":"node_1_1",
				},
				{
					"id":"12",
					"name":"node_1_2",
					"children":[
						{
							"id":"121",
							"name":"node_1_2_1",
						},
						{
							"id":"122",
							"name":"node_1_2_2",
						},
					]
				},
			]
		},
		{
			"id":"2",
			"name":"node_2",
		},
	],
	"onClickTreeNode":callback_function,
}

(2)树形结构渲染: 组件应能正确渲染树形结构,每个节点应清晰展示。

(3)节点单选: 用户点击树形结构中的某个节点时,该节点应被选中,同时其他节点应取消选中状态。

(4)节点状态反馈: 选中的节点应有明显的视觉反馈,如变色或添加特殊标记。

(5)事件监听: 应提供事件监听机制,允许用户监听节点的选中事件,以便在节点被选中时执行特定操作。

3 代码实现

首先创建一个 neat_treewidget.js 文件,该文件用于本组件的工具类、窗体部件基类以及各个实现类的代码构建。

在具体的业务代码编写之前,先实现一个工具类以及一些工具方法,方便后面调用:

class CommonUtil {

    // 设置 DIV 中的文字为水平与垂直居中
    static centerTextInDiv(container) {
        container.style.display = 'flex';
        container.style.textAlign = 'center';
        container.style.justifyContent = 'center';
        container.style.flexDirection = 'column';
    }
}

该工具类中包含一个可以将 DIV 中的文字设置为水平与垂直居中的静态方法。

接下来,定义一个通用的显示窗体的基类:

class NeatBaseWid {
    constructor(container, para) {
        this.container = container;	// 接收调用者传入的 DOM 元素(一般是 DIV)
        this.para = para;	// 保存调用者传入的 para 对象
    }
}

然后开始定义树节点类型:

class NeatTreeNode extends NeatBaseWid {

    static LEVEL_OFFSET = 10;     // 每个级别的树节点偏移像素
    static NODE_HEIGHT = '23px';     // 树节点高度
    static NODE_NAME_FONTSIZE = '14px';     // 默认标题字符串的字体大小
    static NODE_NAME_COLOR = '#000';     // 默认标题字符串字体颜色
    static NODE_OPENCLOSE_ICON_WIDTH = '23px';     // 树节点打开、关闭小箭头图标的宽度
    static NODE_OPENCLOSE_ICON_CLASS_OPEN = 'fa fa-angle-down';			//打开
    static NODE_OPENCLOSE_ICON_CLASS_CLOSE = 'fa fa-angle-right';		//关闭

    constructor(container, para) {
        super(container, para);
        this.id=this.para.id;
        this.name=this.para.name;
        this.parent = para.treeNode ?? null;        // 父节点
        this.level = (para.treeNode && (para.treeNode.level + 1)) ?? 0;   // 节点的级别,最高一级是根节点
        this.nameContainer = null; // 树节点的标题容器
        this.openCloseIconContainer = null; // 树节点的打开、关闭小箭头图标容器
        this.children = [];       // 子节点
        this.childrenContainer = null; // 子节点容器
        this.render();
    }

上面代码定义了 NeatTreeNode 的一些默认属性与成员变量,并且创建构造函数,该函数接收调用者传入的 DIV 容器,并且调用 render 方法。
在 render 方法,需要渲染当前树节点,并且还要根据是否有子节点的情况,创建子节点容器:

	render() {
        this.container.innerHTML = '';  // 清空容器

        // 渲染当前树节点
        let nodeContainer = document.createElement('div');
        nodeContainer.style.display = 'flex';
        nodeContainer.style.width = '100%';
        nodeContainer.style.height = NeatTreeNode.NODE_HEIGHT;
        this.container.appendChild(nodeContainer);

上面代码创建了当前树节点的容器,接下来需要根据当前节点的级别,做一个偏移处理:

		// 如果当前树节点的级别大于 0,则放一个偏移容器
        if (this.level > 0) {
            let offsetContainer = document.createElement('div');
            offsetContainer.style.width = this.level*NeatTreeNode.LEVEL_OFFSET + 'px';
            nodeContainer.appendChild(offsetContainer);
        }

接下来,如果当前树节点有子节点,则显示树节点的打开、关闭小箭头图标,如果没有子节点,则做一个简单的偏移处理即可:

		// 如果当前树节点有子节点,则显示树节点的打开、关闭小箭头图标
        if (this.para.children && this.para.children.length > 0) {
            this.openCloseIconContainer = document.createElement('i');
            this.openCloseIconContainer.className = NeatTreeNode.NODE_OPENCLOSE_ICON_CLASS_OPEN;
            this.openCloseIconContainer.style.width = NeatTreeNode.NODE_OPENCLOSE_ICON_WIDTH;
            this.openCloseIconContainer.style.height = '100%';
            CommonUtil.centerTextInDiv(this.openCloseIconContainer);
            nodeContainer.appendChild(this.openCloseIconContainer);
            
            // 树节点打开或关闭
            let that = this;
            this.openCloseIconContainer.onclick = function () {
                if(NeatTreeNode.NODE_OPENCLOSE_ICON_CLASS_OPEN === that.openCloseIconContainer.className){
                    that.openCloseIconContainer.className = NeatTreeNode.NODE_OPENCLOSE_ICON_CLASS_CLOSE;
                    that.childrenContainer.style.display='none';
                }else{
                    that.openCloseIconContainer.className = NeatTreeNode.NODE_OPENCLOSE_ICON_CLASS_OPEN;
                    that.childrenContainer.style.display='block';
                }
            }
        } else {
            let offsetContainer = document.createElement('div');
            offsetContainer.style.width = NeatTreeNode.NODE_OPENCLOSE_ICON_WIDTH;
            nodeContainer.appendChild(offsetContainer);
        }

注意:上面代码中,在创建打开、关闭小箭头图标后,还定义了点击事件的处理函数。
然后,创建当前的树节点标题,并且定义点击树节点标题时的事件处理函数:

        // 树节点标题
		this.nameContainer = document.createElement('div');
        this.nameContainer.style.flexGrow = '1';
        this.nameContainer.innerText = this.para.name;
        CommonUtil.centerTextInDiv(this.nameContainer);
        this.nameContainer.style.textAlign = 'left';
        this.nameContainer.style.cursor = 'pointer';
        nodeContainer.appendChild(this.nameContainer);
		
        // 点击树节点的触发动作
        let that = this;
        this.nameContainer.onclick = function () {
            that.para.onClickTreeNode(that);
        }

最后,根据是否有子节点的情况,创建子节点容器:

        // 创建子树节点
        if(this.para.children && this.para.children.length > 0){
            this.childrenContainer = document.createElement('div');
            this.childrenContainer.style.width = '100%';
            this.container.appendChild(this.childrenContainer);
            this.para.children.forEach(element => {
                let nodeContainer = document.createElement('div');
                this.childrenContainer.appendChild(nodeContainer);
                element.treeNode = this;
                element.onClickTreeNode = this.para.onClickTreeNode;
                let treeNode = new NeatTreeNode(nodeContainer, element);
                this.children.push(treeNode);
            });
        }
	}

至此,完成了整个渲染函数 render 的构建。

下一步,创建树结构类型:

class NeatHeaderTreeWidget extends NeatBaseWid {

    constructor(container, para) {
        super(container, para);
        this.rootTreeNodes = [];      // 根节点集合
        this.render();
    }

    render() {
        // 清空容器
        this.container.innerHTML = '';

        // 渲染树结构
        this.container.style.width = '100%';
        this.container.style.height = '100%';

        // 创建树节点
        this.para.nodes.forEach(element => {
            let nodeContainer = document.createElement('div');
            this.container.appendChild(nodeContainer);
            element.onClickTreeNode = this.para.onClickTreeNode;
            let treeNode = new NeatTreeNode(nodeContainer, element);
            this.rootTreeNodes.push(treeNode);
        });
    }
}

注意:创建树结构类型的渲染函数 render 中的创建树节点实际是创建根节点,至于子节点则通过根节点的渲染函数创建(是一个递归创建的过程)。

完成树结构组件的代码编写后,可以创建 neater_treewidget.html 文件,调用树结构组件:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>header tab</title>
    <style>
        html {
            height: 100%;
        }

        body {
            margin: 0;
            height: 100%;
        }
    </style>
    <link rel="stylesheet" href="./font-awesome-4.7.0/css/font-awesome.css">
</head>

<body>
    <div id="divMain" style="height: 100%;width: 100%;"></div>
</body>
<script src="./neat_treewidget.js"></script>
<script>
    let para = {
        "nodes": [
            {
                "id": "1",
                "name": "根节点 node_1",
                "children": [
                    {
                        "id": "11",
                        "name": "一级子节点 node_1_1",
                    },
                    {
                        "id": "12",
                        "name": "一级子节点 node_1_2",
                        "children": [
                            {
                                "id": "121",
                                "name": "二级子节点 node_1_2_1",
                            },
                            {
                                "id": "122",
                                "name": "二级子节点 node_1_2_2",
                            },
                        ]
                    },
                ]
            },
            {
                "id": "2",
                "name": "根节点 node_2",
            },
            {
                "id": "3",
                "name": "根节点 node_3",
                "children": [
                    {
                        "id": "31",
                        "name": "一级子节点 node_3_1",
                    },
                ]
            },
            {
                "id": "4",
                "name": "根节点 node_4",
            },
        ],
        "onClickTreeNode":clickTreeNode,
    };

    function clickTreeNode(treeNode){
        alert(treeNode.name);
    }

    let treeWidget = new NeatHeaderTreeWidget(document.getElementById('divMain'), para);

</script>

</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/433920.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DR模式LVS负载均衡部署

实验&#xff1a;7-1做调度器&#xff1b;7-2和7-3做真实服务器&#xff1b;7-4做客户端&#xff1b; 1.先关闭所有的防火墙和selinux [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 [rootlocalhost ~]# 2.怎么看selinux状态 [rootlocalhost…

docker拉取镜像报错permission denied

ocker pull apache/causeway-app-helloworld:latest permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post “http://%2Fvar%2Frun%2Fdocker.sock/v1.24/images/create?fromImageapache%2Fcauseway-app-helloworld&a…

Vue:双token无感刷新

文章目录 初次授权与发放Token&#xff1a;Access Token的作用&#xff1a;Refresh Token的作用&#xff1a;无感刷新&#xff1a;安全机制&#xff1a;后端创建nest项目AppController 添加login、refresh、getinfo接口创建user.dto.tsAppController添加模拟数据 前端Hbuilder创…

node的使用和模块化认识

node使用 1. node运行文件 node执行js的方式是在cmd命令行运行运行方式两种 直接打开命令行输入node&#xff0c;进入node环境&#xff0c;书写javascript&#xff0c;这种方式书写javascript关闭命令行就需要在重新写一遍&#xff0c;一般开发不推荐使用这种方式。 退出node…

磁盘没有满 为什么提示磁盘空间不足?原来是inode惹的祸。

我为什么知道是inode 的问题呢&#xff1f; 接下备好瓜子花生来且听我分析 我一个免费开源根据ip获取用户地理位置的api 突然报错如下 failed to open stream: No space left on device in 然后登录linux 使用shell命令 自动补全功能竟然也提示磁盘空间不足 报错如下 cd /-b…

ES单节点部署

ES 拉取镜像 docker pull elasticsearch:7.10.1启动容器 docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" -e "ES_JAVA_OPTS-Xms1g -Xmx1g" -v /es_data:/usr/share/elasticsearch/data --name es 558380375f1a注&#xff1a…

微信小程序怎么盈利?探索微信小程序的盈利途径与建设流程

微信小程序自推出以来&#xff0c;凭借其无需下载安装、即用即走的特点&#xff0c;在移动互联网领域迅速占据了重要地位。商家和开发者们纷纷投入其中&#xff0c;希望借助这一平台实现商业价值的转化。那么&#xff0c;微信小程序是如何为开发者和商家带来盈利的呢&#xff1…

配置与管理防火墙

配置与管理防火墙 1&#xff0c;概念&#xff1a;设置在不同网络或网络安全域之间的一系列部件的组合。 2&#xff0c;功能&#xff1a;保护内网中易手攻击的服务&#xff1b;控制内外网之间网络系统的访问&#xff1b;隐藏内网的IP地址及结构的细节&#xff0c;提高网络保护…

【操作系统概念】 第5章:进程调度

文章目录 0.前言5.1 基本概念5.1.1 CPU-I/O 区间周期5.1.2 CPU程序调度5.1.3 抢占调度5.1.4 分派程序 5.2 调度准则5.3 调度算法5.3.1 先到先服务调度(First-Come&#xff0c;First-Served scheduling)5.3.2 最短作业优先调度(shortest-job-first scheduling,SJF)5.3.3 优先级调…

docker 安装 portainer

小编给友友们总结了一下 Portainer 的好处以下 Portainer是Docker的图形化管理工具&#xff0c;提供状态显示面板、应用模板快速部署、容器镜像网络数据卷的基本操作&#xff08;包括上传下载镜像&#xff0c;创建容器等操作&#xff09;、事件日志显示、容器控制台操作、Swar…

掘根宝典之C语言原码,反码,补码,位操作运算符(~,,|,^,<<,>>,=,|=,^=,>>=,<<=)

目录 二进制数 什么是二进制数 c语言中的二进制数 机器数 原码 正数计算 负数计算 反码 负数计算 跨零计算 补码 定义 跨零计算 总结 按位逻辑运算符&#xff08;~&#xff0c;&&#xff0c;&,|&#xff0c;|&#xff0c;^&#xff0c;^&#xff09; 按…

玩家至上:竞技游戏设计如何满足现代玩家的需求?

文章目录 一、现代玩家需求分析二、以玩家体验为核心的游戏设计三、个性化与定制化服务四、强化社交互动与社区建设五、持续更新与优化《游戏力&#xff1a;竞技游戏设计实战教程》亮点编辑推荐内容简介目录获取方式 随着科技的飞速发展和游戏产业的不断壮大&#xff0c;现代玩…

java工程师面试技巧,最新Java开发面试解答

一、前言 聊的是八股的文&#xff0c;干的是搬砖的活&#xff01; 面我的题开发都用不到&#xff0c;你为什么要问&#xff1f;可能这是大部分程序员求职时的经历&#xff0c;甚至也是大家讨厌和烦躁的点。明明给的是拧螺丝的钱、明明做的是写CRUD的事、明明担的是成工具的人…

基于词袋模型的场景识别(附源代码!!!)

目录 1. 任务要求2. 数据集3. 实现算法4. 实验结果5. 源代码 1. 任务要求 输入&#xff1a;给定测试集图片&#xff0c;预测在15个场景中的类别。任务&#xff1a; 实现Tiny images representation。实现最近邻分类器nearest neighbor classifier。实现SIFT特征词袋表示 输出&…

原生IP是什么?如何获取海外原生IP?

一、什么是原生IP 原生IP地址是互联网服务提供商&#xff08;ISP&#xff09;直接分配给用户的真实IP地址&#xff0c;无需代理或转发。这类IP的注册国家与IP所在服务器的注册地相符。这种IP地址直接与用户的设备或网络关联&#xff0c;不会被任何中间服务器或代理转发或隐藏。…

嵌入式学习-FreeRTOS-Day1

一、重点 1、VCC和GND VCC&#xff1a; 1、电路中为电源&#xff0c;供应电压 2、3.3v-5v 3、数字信号中用1表示GND&#xff1a; 1、表示地线 2、一般为0v 3、数字信号中用0表示2、电容和电阻 电容 存储电荷 存储能量&#xff1a; 电容器可以在其两个导体板&#xff08;极…

java开发工程师面试技巧,小白必看

什么是分布式锁&#xff1f;在回答这个问题之前&#xff0c;我们先回答一下什么是锁。 普通的锁&#xff0c;即在单机多线程环境下&#xff0c;当多个线程需要访问同一个变量或代码片段时&#xff0c;被访问的变量或代码片段叫做临界区域&#xff0c;我们需要控制线程一个一个…

随机变量及其分布错题本

《1800》 1 需要从概率密度出发&#xff0c;在积分成为分布函数的情况下将 x 拉回为 -x来进行计算&#xff0c;所以X与-X最后得出的分布函数会一样。 2 3 4 5 6 7 8 9 10 11 12 13 14 15

二维码门楼牌管理系统应用场景:市场研究机构的新宠

文章目录 前言一、市场研究机构的新工具二、市场分析与区域趋势研究三、支持企业决策与市场营销策略四、与市场研究机构的联动效应五、未来展望 前言 在数字化时代&#xff0c;二维码门楼牌管理系统以其独特的优势&#xff0c;正在成为市场研究机构的新宠。通过收集和分析门牌…

Linux常用命令之top监测

(/≧▽≦)/~┴┴ 嗨~我叫小奥 ✨✨✨ &#x1f440;&#x1f440;&#x1f440; 个人博客&#xff1a;小奥的博客 &#x1f44d;&#x1f44d;&#x1f44d;&#xff1a;个人CSDN ⭐️⭐️⭐️&#xff1a;传送门 &#x1f379; 本人24应届生一枚&#xff0c;技术和水平有限&am…