jQuery + bootstrap 实现下拉多选列表

多级下拉菜单

  • 基本思路
  • HTML代码
  • CSS样式
  • JS代码

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

jQuery + bootstrap 实现下拉多选列表_第1张图片

基本思路

主要是基于 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 = `
  • ${v.appId}">${v.appName}
  • `
    ; $(`.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 = $(`
  • ${v.appId}">${v.appName}
  • `
    ); // 往碎片化文档中添加的元素必须是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();
    });
    

    你可能感兴趣的:(jQuery,bootstrap,js,css,前端,bootstrap,html)