jQuery + bootstrap 实现下拉多选列表


前段时间做的页面里要有一个二级多选的下拉列表,项目用的是 jQuery 和 bootstrap,找了很多方法都要额外引入插件,但是我不想为了这个功能单独引入一个插件,决定自己基于 bootstrap 的下拉菜单组件做一个,先展示一下效果。

在这里插入图片描述

基本思路

主要是基于 bootstrap 提供的下拉菜单组件,嵌套一个子菜单,并结合复选框实现了二级多选功能。二级选择框中具体选项的相关数据都是从后端拿的,所以还会涉及到动态插入元素的处理方法,下面一步步地进行介绍。

HTML代码

基于 bootstrap 提供的下拉菜单组件,在下拉菜单中嵌套菜单,二级菜单的具体选项动态插入的,HTML 代码如下。

<div class="container">
  <div class="row">
    <label class="col-xs-2 col-xs-offset-1 control-label text-center">应用列表</label>
    <div class="col-xs-6 dropdown">
      <input type="hidden" id="checkedList">
      <div id="checkedNameList" class="form-control text-center dropdown-toggle" data-toggle="dropdown"></div>
      <ul class="dropdown-menu col-xs-6" role="menu">
        <li data-index="01" class="dropdown-submenu">
          <label>社交</label>
          <ul class="dropdown-menu col-xs-11"></ul>
        </li>
        <li data-index="02" class="dropdown-submenu">
          <label>游戏</label>
          <ul class="dropdown-menu col-xs-11"></ul>
        </li>
        <li data-index="03" class="dropdown-submenu">
          <label>视频</label>
          <ul class="dropdown-menu col-xs-11"></ul>
        </li>
        <li data-index="04" class="dropdown-submenu">
          <label>购物</label>
          <ul class="dropdown-menu col-xs-11"></ul>
        </li>
        <li data-index="05" class="dropdown-submenu">
          <label>音乐</label>
          <ul class="dropdown-menu col-xs-11"></ul>
        </li>
        <li data-index="06" class="dropdown-submenu">
          <label>下载</label>
          <ul class="dropdown-menu col-xs-11"></ul>
        </li>
        <li data-index="07" class="dropdown-submenu">
          <label>网址</label>
          <ul class="dropdown-menu col-xs-11"></ul>
        </li>
      </ul>
    </div>
  </div>
</div>

CSS样式

主要是想通过 CSS 实现以下几个效果:

  • 箭头小图标
  • 更改滚动条的样式
  • 一级菜单与二级菜单的布局
  • 鼠标悬停在一级菜单的选项上时,显示对应的二级菜单
/* 显示所有已选择的应用名 */
#checkedNameList {
  min-height: 34px;
  height: auto;
}
.dropdown-toggle::after{
  display: block;
  content: " ";
  float: right;
  width: 0;
  height: 0;
  border-color: transparent;
  border-style: solid;
  border-width: 4px 4px 0 4px;
  border-top-color: #555;
  margin-top: 10px;
}
.dropdown-menu {
  text-align: center;
  left: 15px;
  top: auto;
  padding: 0;
}
/* 更改滚动条样式 */
.dropdown-menu::-webkit-scrollbar {
  width: 4px;
  border-radius: 4px;
}
/* 更改滚动条的轨道样式 */
.dropdown-menu::-webkit-scrollbar-track {
  border-radius: 4px;
}
/* 更改滚动条的滑块样式 */
.dropdown-menu::-webkit-scrollbar-thumb {
  background-color: #d6d6d6;
  border-radius: 4px;
}
/* 菜单中的选项 */
.dropdown-menu > li > label {
  width: 100%;
  padding: 2px 12px;
  margin: 0;
  font-weight: 400;
}
/* 鼠标悬停处的选项时更改背景色 */
.dropdown-menu > li:hover {
  background-color: #ebebeb;
}
/* 一级菜单中的右箭头小图标 */
.dropdown-submenu > label::after {
  display: block;
  content: " ";
  float: right;
  width: 0;
  height: 0;
  border-color: transparent;
  border-style: solid;
  border-width: 4px 0 4px 4px;
  border-left-color: #ccc;
  margin-top: 4px;
}
.dropdown-submenu:hover > label::after {
  border-left-color: #000;
}
/* 二级菜单的位置 */
.dropdown-submenu > .dropdown-menu { 
  top: -1px;
  left: 100%;
  height: 170px;
  margin: 0;
  border-left: none;
  overflow: auto;
}
/* 二级菜单中的多选框向右浮动 */
.dropdown-submenu > .dropdown-menu input {
  float: right;
}
/* 鼠标悬停在一级菜单的选项时显示对应的二级菜单 */
.dropdown-submenu:hover > .dropdown-menu { 
  display: block;
}

JS代码

现在需要考虑如何将获取到的选项数据插入到页面中。

// 假设获取到的数据如下
var data_list = [
  {appId: "01-0001", appName: "QQ"},
  {appId: "01-0002", appName: "微信"},
  {appId: "02-0001", appName: "王者荣耀"},
  {appId: "02-0002", appName: "和平精英"},
  {appId: "03-0001", appName: "腾讯视频"},
  {appId: "03-0002", appName: "快手短视频"},
  {appId: "04-0001", appName: "淘宝"},
  {appId: "04-0002", appName: "京东"},
  {appId: "05-0001", appName: "QQ音乐"},
  {appId: "05-0002", appName: "网易云音乐"},
  {appId: "06-0001", appName: "迅雷下载"},
  {appId: "06-0002", appName: "百度网盘"},
  {appId: "07-0001", appName: "百度"},
  {appId: "07-0002", appName: "谷歌"},
];

最基础的办法,遍历数据集,直接将每一项数据插入到二级菜单中,代码如下。

// 动态插入二级菜单的选项
data_list.forEach(v => {
  var category = v.appId.slice(0, 2);
  var opt = `<li data-value="${v.appId}"><label>${v.appName}<input type="checkbox"></label></li>`;
  $(`.dropdown-submenu[data-index=${category}]`).find(".dropdown-menu").append(opt);
});

但这个方法有一个缺陷,每一次遍历都会向 DOM 中插入一个新的 li 元素。我们知道,向DOM树中新增一个DOM节点会导致回流。这还只是数据比较少的情况下,但是数据量很大的话怎么办?也太消耗性能了,此时文档片段就起了很重要的作用,先把同一类的选项放到一个文档片段中,然后再一次性添加到对应的二级菜单中,因为文档片段存在于内存中,不在DOM树里,所以把子元素插入到碎片化文档的操作不会导致回流,这样能有效地减少回流次数,代码如下。

// 对数据进行分类,并设置每个类对应的文档片段
var app_list = data_list.reduce((now, v) => {
  var c = v.appId.slice(0, 2);
  if (!now.hasOwnProperty(c)) {
    now[c] = document.createDocumentFragment();
  }
  var opt = $(`<li data-value="${v.appId}"><label>${v.appName}<input type="checkbox"></label></li>`);
  // 往碎片化文档中添加的元素必须是DOM节点
  // jQuery生成的是jQuery对象,这是一个类数组对象,它的第一个元素就是对应的DOM节点
  now[c].appendChild(opt[0]);
  return now;
}, {});
// 将每个类的文档片段插入到相应的菜单中
for (var [k, v] of Object.entries(app_list)) {
  $(`.dropdown-submenu[data-index=${k}]`).find(".dropdown-menu").append(v);
}

现在解决了动态插入的问题,看看如何实现第一次点击选中、第二次点击取消选中并显示对应的结果。因为选项是动态插入的,为了保证 input 上的事件能被触发,我们将 input 元素上的事件委托到父元素上,点击元素时,如果当前是选中状态,则将当前选项的应用名和应用ID存入,若取消选中,则将应用名和应用ID删除,代码如下。

$(".dropdown-submenu > .dropdown-menu").on('click', 'input', function(e){
  // 当前选项的应用ID
  var nowId = $(this).parent().parent().attr("data-value");
  // 当前选项的应用名
  var nowName = $(this).parent().text();
  // 所有已选中的应用ID
  var list = !$("#checkedList").val() ? [] : $("#checkedList").val().split(" ");
  // 所有已选中的应用名
  var listName = !$("#checkedNameList").text() ? [] : $("#checkedNameList").text().split(" ");
  var index = list.indexOf(nowId);
  if ($(this).is(":checked")) {
  	// 当前选项被选中,将当前选项的应用ID和应用名存入已选中的应用ID和应用名数组中
    if (index === -1) {
      list.push(nowId);
      listName.push(nowName);
    }
  } else {
  	// 当前选项取消选中,将当前选项的应用ID和应用名从已选中的应用ID和应用名数组中删除
    if (index !== -1) {
      list.splice(index, 1);
      listName.splice(index, 1);
    }
  }
  $("#checkedList").val(list.join(" "));
  $("#checkedNameList").text(listName.join(" "));
});

bootstrap 提供的下拉菜单组件在点击了一个选项之后,下拉菜单会消失,但是现在需要能进行多选,所以这个情况需要避免。在点击时,实际上点击的是 li 的子元素,但是由于事件冒泡,点击事件会冒泡至 li 元素导致下拉菜单被隐藏。可以通过阻止事件冒泡解决这个问题,代码如下。

// 注意最好不要直接为label元素绑定事件,因为二级菜单的选项是动态生成的。这里没有体现出来,但实际上选项的数据是异步获取的
// 可以使用jQuery的事件代理,将事件绑定到父元素上
$(".dropdown-menu").on('click', 'label', function(e){
	// 阻止事件冒泡到li元素,避免出现点击一个选项后列表消失的情况
  e.stopPropagation();
});
  • 5
    点赞
  • 5
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ostuthere

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值