资讯专栏INFORMATION COLUMN

徒手撸UI之Cascader

junnplus / 1963人阅读

摘要:但是如果一刹那我不想选江疏影了,我想选张雨绮因为胸大,首先我要从霍思燕换到高圆圆,然后转到张雨绮,选中展示出来,这时候就要先删除霍思燕,然后把高圆圆和张雨绮进来。

QingUI是一个UI组件库
目前拥有的组件:DatePicker, TimePicker, Paginator, Tree, Cascader, Checkbox, Radio, Switch, InputNumber, Input
ES6语法编写,无依赖
原生模块化,Chrome63以上支持,请开启静态服务器预览效果,静态服务器传送门
采用CSS变量配置样式
辛苦造轮子,欢迎来github仓库star:QingUI
写在前面

去年年底项目中尝试着写过一个分页的Angular组件,然后就有了写QingUI的想法

过程还是非常有意思的

接下来我会用几篇文章分别介绍每个组件的大概思路,请大家耐心等待

这一篇介绍Cascader级联选择器

点个star就是对我最好的支持

repo: QingUI
少废话,先上图

数据

既然是级联选择器,数据肯定是树形结构,像这样

const data = [
    {
        label: "霍思燕",
        sub: [
            {
                label: "江疏影",
            },
            {
                label: "倪妮",
            },
        ],
    },
    {
        label: "高圆圆",
        sub: [
            {
                label: "张雨绮",
            },
            {
                label: "宋佳",
            },
        ],
    },
];

你猜我最喜欢哪位女明星 :)

而且用户点到哪一个label上,就呼出下一级列表

那么问题来了,我怎么知道我当前在哪一级,又在哪一个分支?

所以我需要给每一个分支做标记,点到哪一个label上,就取出标记,该标记能指引我找到对应的数据分支

我的方法是这样的,新建一个字段queue,字段值是一个字符串形式的数字,位数表示当前是第几级,数字表示当前是第几个分支

比如说queue = 131;,表示当前在第一个分支下面的第三个分支下面的第一个分支

buildQueue(data, queue = "") {
    for (let i = 0; i < data.length; i++) {
        const item = data[i];
        const sub = item.sub;
        const newQueue = `${queue}${i}`;
        item[this.queue] = newQueue;
        if (sub) {
            this.buildQueue(sub, newQueue);
        }
    }
}

字段名其实我并没有用queue,因为有可能已经被占用,我用的是时间戳组成的hash值,所以赋值的时候要写成item[this.queue] = newQueue;

渲染的时候给DOM元素加一个data-v="${item[this.queue]}就行了

那么怎么读呢?

这些数字其实就是数组的索引,加一个递归搞定

findSubByQueue(data, queue) {
    const n = Number.parseInt(queue.charAt(0));
    for (let i = 0; i < data.length; i++) {
        if (i === n) {
            if (queue.length > 1) {
                return this.findSubByQueue(data[i].sub, queue.slice(1));
            } else {
                return data[i].sub;
            }
        }
    }
}
事件

级联肯定要支持点击和悬浮两种事件触发机制

所以我用this.eventType来保存事件类型,其实两种事件大部分代码是可以复用的

for (let i = 0; i < $trunks.length; i++) {
    const $trunk = $trunks[i];
    const v = $trunk.dataset.v;
    const label = $trunk.querySelector(".label").innerHTML;
    const CL = $trunk.classList;
    $trunk.addEventListener(this.eventType, function(event) {
        event.stopPropagation();
        // 遍历清除trunk的active
        self.removeTrunkActive($trunks);
        // 当前trunk变成active
        CL.add("active");
        // 构建路径
        self.buildPath(v.length, label, false);
        // 找到子数据
        const sub = self.findSubByQueue(self.data, v);
        // 填充子board
        $subBoard.innerHTML = self.renderCascade(sub);
        // 添加事件
        self.$rowEvent($subBoard);
    });
}

注释也写的很清楚,首先是一个高亮的处理,然后要把当前路径保存下来,通过queue找到子数据,然后渲染出来,最后给子节点添加事件

要知道,分支可以分为两种,一种是下面还有分支,我把它称作trunk,另一种是末梢,下面没有分支了,我把它称作leaf

点击很容易,只给trunk添加事件就可以了

但是悬浮,leaf也要有事件,就是把之前的高亮和子数据清空

if (this.trigger === "hover") {
    for (let i = 0; i < $leafs.length; i++) {
        $leafs[i].addEventListener("mouseenter", function() {
            self.removeTrunkActive($trunks);
            $subBoard.innerHTML = "";
        });
    }
    // 离开curtain
    this.$curtain.addEventListener("mouseleave", function() {
        self.removeTrunkActive(self.$trunks);
        self.$subBoard.innerHTML = "";
    });
}

那么怎么选中呢?

到leaf才是一个完整的路径,所以leaf特殊处理,无论是什么事件,点击leaf选中,把路径渲染出来

路径

保存路径是一个动态的过程

因为我可能查看了某一个分支,然后又查看另一个分支,最终选中了别的分支

所以保存路径要根据路径的长度和当前级别来确定是添加还是删除

比如我现在在江疏影这里,还没有选中,那么当前是第二级,路径里只保存了霍思燕,如果我选中,那么简单,直接把江疏影push到数组里,展示出来。但是如果一刹那我不想选江疏影了,我想选张雨绮(因为胸大),首先我要从霍思燕换到高圆圆,然后转到张雨绮,选中展示出来,这时候就要先删除霍思燕,然后把高圆圆和张雨绮push进来。

buildPath(level, label, render) {
    if (this.path.length < level) {
        // 往下选择,直接push
        this.path.push(label);
    } else {
        // 退回选择,根据退回长度删除path元素,再push
        this.path = [...this.path.slice(0, level - 1), label];
    }
    if (render) {
        this.renderPath();
    }
}
搜索

突发奇想,我又想加一个搜索功能

比如说我现在搜集了好几千个女明星,打乱格式化成树形数据,那我选择起来可就困难了,难道每一个分支都查看一遍吗?如果有搜索,我只要搜张雨绮,所有包含张雨绮的级联都展示出来,是不是方便很多!

看起来很复杂的样子

其实,换一个思路,初始化的时候就把所有的路径都遍历出来,缓存在一个数组里,搜索的时候只要检索这些字符串有没有张雨绮,是不是回到我们熟悉的字符串操作上来了?

遍历所有路径

iterateAllPath() {
    const self = this;
    let temp = [];
    const data = pathPush([...this.data]);
    function pathPush(data, arr = []) {
        for (const item of data) {
            item.path = [];
            // 将路径存入item中的数组
            item.path.push(...arr, item.label);
        }
        return data;
    }
    function recursive(data) {
        for (const item of data) {
            const sub = item.sub;
            if (sub) {
                // 将下一层放入temp
                temp.push(...pathPush(sub, item.path));
            } else {
                // 没有下一层则路径结束
                self.pathPool.push(item.path.join(self.seperator));
            }
        }
        if (temp.length) {
            // 重新初始化
            data = temp;
            temp = [];
            recursive(data);
        }
    }
    recursive(data);
}
for (const item of this.pathPool) {
    const match = item.match(reg);
    if (!match) {
        continue;
    }
    result.push(item);
}

别急,还有需求,我想把关键词高亮

比如说我搜张雨绮,所有结果中张雨绮都要高亮

我搜张雨,所有结果中张雨都要高亮

这个也不复杂,用关键词把路径截成三段,如果关键词在首尾那就截成两段

这里有一个小问题,如果分隔符与关键词之间有空格,展示结果总是不符合预期

后来才发现,如果标签内第一个字符是空格,空格会被忽略

所以还需要小小的处理一下

const reg = new RegExp(value, "i");
for (const item of this.pathPool) {
    const match = item.match(reg);
    if (!match) {
        continue;
    }
    result.push(item);
    const index = match.index;
    let [left, center, right] = [item.slice(0, index), match[0], item.slice(index + value.length)];
    // 如果标签内第一个字符是空格,空格会被忽略
    if (right && right.startsWith(" ")) {
        right = ` ${right.trimLeft()}`;
    }
    tpl += `
        
${left ? `${left}` : ""} ${center} ${right ? `${right}` : ""}
`; } if (!tpl) { tpl = "
No Result
"; }
写在后面

Cascader比较核心的逻辑就在这里了

相较前几篇文档,隔的时间有点长,不过Cascader不会让你失望的

如果觉得QingUI还不错,点个star激励一下老夫

repo: QingUI

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95316.html

相关文章

  • 徒手UIDatePicker

    摘要:是一个组件库目前拥有的组件语法编写,无依赖原生模块化,以上支持,请开启静态服务器预览效果,静态服务器传送门采用变量配置样式辛苦造轮子,欢迎来仓库四月份找工作,求内推,坐标深圳写在前面去年年底项目中尝试着写过一个分页的组件,然后就有了写的想法 QingUI是一个UI组件库目前拥有的组件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...

    zilu 评论0 收藏0
  • 徒手UIPaginator

    摘要:是一个组件库目前拥有的组件语法编写,无依赖原生模块化,以上支持,请开启静态服务器预览效果,静态服务器传送门采用变量配置样式辛苦造轮子,欢迎来仓库四月份找工作,求内推,坐标深圳写在前面去年年底项目中尝试着写过一个分页的组件,然后就有了写的想法 QingUI是一个UI组件库目前拥有的组件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...

    liuhh 评论0 收藏0
  • 徒手UITimePicker

    摘要:是一个组件库目前拥有的组件语法编写,无依赖原生模块化,以上支持,请开启静态服务器预览效果,静态服务器传送门采用变量配置样式辛苦造轮子,欢迎来仓库四月份找工作,求内推,坐标深圳写在前面去年年底项目中尝试着写过一个分页的组件,然后就有了写的想法 QingUI是一个UI组件库目前拥有的组件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...

    Codeing_ls 评论0 收藏0
  • 徒手UITree

    摘要:是一个组件库目前拥有的组件语法编写,无依赖原生模块化,以上支持,请开启静态服务器预览效果,静态服务器传送门采用变量配置样式辛苦造轮子,欢迎来仓库四月份找工作,求内推,坐标深圳写在前面去年年底项目中尝试着写过一个分页的组件,然后就有了写的想法 QingUI是一个UI组件库目前拥有的组件:DatePicker, TimePicker, Paginator, Tree, Cascader, ...

    2i18ns 评论0 收藏0
  • 徒手框架--高并发环境下的请求合并

    摘要:我们就可以将这些请求合并,达到一定数量我们统一提交。总结一个比较生动的例子给大家讲解了一些多线程的具体运用。学习多线程应该多思考多动手,才会有比较好的效果。地址徒手撸框架系列文章地址徒手撸框架实现徒手撸框架实现 原文地址:https://www.xilidou.com/2018/01/22/merge-request/ 在高并发系统中,我们经常遇到这样的需求:系统产生大量的请求,但是这...

    刘东 评论0 收藏0

发表评论

0条评论

junnplus

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<