js面向对象封装级联下拉菜单列表的实现步骤


Posted in Javascript onFebruary 08, 2021

本实例开发的级联下拉菜单是根据已有json数据创建的DOM元素。点击文本框后,显示一级菜单。如果菜单中包含子菜单,菜单右侧会有指示箭头。点击菜单之后,会再显示下一级菜单,以此类推。当菜单下无子菜单时,选择菜单后会在文本框中显示。

打开后的级联菜单效果如图所示:

js面向对象封装级联下拉菜单列表的实现步骤

使用实例中封装好的插件,只需要有一个input元素,即可通过插件自动生成级联下拉菜单,html代码如下所示:

<div style="margin-top:100px;text-align:center;">
  <input type="text" id="input">
 </div>

接下来看下具体封装的js代码怎么实现。

1. 声明级联菜单的构造函数

构造函数需要传入一个文本框元素和菜单关联数据两个参数。

//elem为文本框,data为菜单关联数据
 function CascadeMenu(elem,data){

 }

2. 在构造函数中创建级联菜单相关元素,并放到页面中,具体代码如下:

function CascadeMenu(elem,data){
  //获取文本框
  this.eInput = elem;
  //设置文本框为只读
  this.eInput.setAttribute('readonly',true);
  //设置文本框提示
  this.eInput.placeholder = '请选择';
  //获取文本框父元素
  var eInputParent = this.eInput.parentNode;
  //创建级联菜单容器
  this.eCascade = document.createElement('div');
  this.eCascade.className = 'cascade_container';
  //创建菜单下拉列表容器
  this.eCascadeInto = document.createElement('div');
  this.eCascadeInto.className = 'cascade_into';
  //下拉列表容器默认隐藏
  this.eCascadeInto.style.display = 'none';
  //将各个元素放到页面中
  this.eCascade.appendChild(this.eInput);
  this.eCascade.appendChild(this.eCascadeInto);
  eInputParent.appendChild(this.eCascade);
  //获取菜单数据
  this.aData = data;
  //记录已选择的菜单数据
  this.aSelected = [];
  //菜单打开状态,默认为false,表示隐藏
  this.bShow = false;
 }

3. 在文本框上绑定点击事件,生成级联下拉菜单

刚才已经把需要的元素都放到了页面中,现在可以通过点击文本框显示和隐藏级联菜单元素;
在显示级联菜单元素时,应该要通过数据生成级联下拉菜单。
因为每次点击都需要生成,所以可以在构造函数的原型上添加一个方法。如下所示: 

function CascadeMenu(elem,data){
  /*…*/

  this.eInput.addEventListener('click',()=>{
   //判断菜单打开状态
   if(this.bShow){ 
    //如果已打开,则隐藏菜单
    this.eCascadeInto.style.display = 'none';
    //修改菜单打开状态
    this.bShow = false;
   }else{
    //显示级联菜单元素
    this.eCascadeInto.style.display = 'none';
    //保存已选择的菜单数据
    this.aSelected = this.eInput.value.split('>');
    //生成级联菜单
    this.generateMenu();
   }
  });
 }
 //根据数据生成级联菜单
 CascadeMenu.prototype.generateMenu = function(){
  //在fnCreatHTML调用实例对象需要声明一个变量指向this
  var _self = this;
  //因为不确定子菜单有多少组,所以需要声明一个函数来递归调用
  //data:传入数据,step:当前级别
  function fnCreatHTML(data,step){
   //用于存储子菜单数据
   var aChildArr = null;
   //生成菜单DOM的字符串
   var sHTML = '<ul>';
   //循环数据
   for(let i=0;i<data.length;i++){
    //判断如果有子菜单,添加child的class,用于显示菜单右侧箭头
    if(data[i].child){ 
     //判断是否是当前选择,如果是,加上cur class,并且存储子菜单数据
     if(data[i].name==this.aSelected[step]){
      aChildArr = data[i].child;
      sHTML += '<li class="child cur" data-po="'+step+'">';
     }else{
      sHTML += '<li class="child" data-po="'+step+'">';
     }
    }else{ 
     //如果没有子菜单,直接加到菜单列表中
     sHTML += data[i].name == this.aSelected[step]?
                  '<li class="cur" data-po="'+step+'">':
                  '<li data-po="'+step+'">';
    }
    //添加菜单名称
    sHTML += data[i].name;
    //结束当前菜单
    sHTML += '</li>';
   }
   sHTML += '</ul>';
   //如果已选择多个菜单,递归调用函数,生成子菜单
   if(this.aSelected.length>step+1){
    sHTML += fnCreatHTML(aChildArr,step+1);
   }
   return sHTML;
  }
  this.eCascadeInto.innerHTML = fnCreatHTML(this.aData,0);
 }

4. 菜单上绑定事件,用于选择菜单

级联菜单有两种类型,一种是有下级菜单的,点击时显示下级菜单;
一种是没有下级菜单的,点击时直接选择菜单并在文本框中按级别显示所选择的菜单。代码如下所示:

function CascadeMenu(elem,data){
  /*…*/

  //利用事件委托选择菜单
  this.eCascadeInto.addEventListener('click',(event)=>{
   //获取菜单
   var eTarget = event.target;
   //获取选择的级别
   var po = +eTarget.dataset.po;
   //删除当前选择级后面的数据
   this.aSelected.splice(po+1,this.aSelected.length-(po+1));
   //修改当前选择数据
   this.aSelected[po] = eTarget.innerHTML;
   //判断是否有子菜单
   if(eTarget.className.indexOf('child')==-1){ //没有子菜单直接选择
    this.eInput.value = this.aSelected.join('>');
    this.eCascadeInto.style.display = 'none';
    this.bShow = false;
   }else{ //有子菜单显示下一级
    //重新生成DOM元素,数组中增加空字符串用于显示下一级
    this.aSelected.push('')
    //重新生成级联菜单
    this.generateMenu();
   }  
  });
 }

5. 在页面空白处点击时,隐藏菜单

现在只能在文本框上点击显示和隐藏菜单。一般来说任何打开的弹框,都希望在弹框以外的位置可以关闭掉。这样需要修改一下文本框上的点击事件函数:当打开菜单时,要在document元素上绑定点击事件,用于关闭菜单;当隐藏菜单时,需要取消document上绑定的点击事件。如下所示:

function CascadeMenu(elem,data){
  /*…*/

  this.eInput.addEventListener('click',()=>{
   //判断菜单打开状态
   if(this.bShow){ 
    //如果已打开,则隐藏菜单
    this.eCascadeInto.style.display = 'none';
    //修改菜单打开状态
    this.bShow = false;
    //取消document上的事件
    document.onclick = null;
   }else{
    //显示级联菜单元素
    this.eCascadeInto.style.display = 'none';
    //保存已选择的菜单数据
    this.aSelected = this.eInput.value.split('>');
    //生成级联菜单
    this.generateMenu();
    document.onclick = () => {
     //隐藏菜单
     this.eCascadeInto.style.display = 'none';
     //修改菜单打开状态
     this.bShow = false;
     //取消document上的事件
     document.onclick = null;
    }
   }
  });
  //阻止冒泡
  this.eCascade.addEventListener('click',(event)=>{
   event.stopPropagation();
  });

  /*…*/
 }

6. 最后,准备好数据,调用构造函数,生成级联下拉菜单,如下所示:

var json = [
  {
   "name":"北京市","id":"110000","child":[
    {"name":"市辖区","id":"110100","child":[
     {"name":"东城区","id":"110101","child":null},{"name":"西城区","id":"110102","child":null},{"name":"朝阳区","id":"110105","child":null},{"name":"丰台区","id":"110106","child":null},{"name":"石景山区","id":"110107","child":null},{"name":"海淀区","id":"110108","child":null},{"name":"门头沟区","id":"110109","child":null},{"name":"房山区","id":"110111","child":null},{"name":"通州区","id":"110112","child":null},{"name":"顺义区","id":"110113","child":null},{"name":"昌平区","id":"110114","child":null},{"name":"大兴区","id":"110115","child":null},{"name":"怀柔区","id":"110116","child":null},{"name":"平谷区","id":"110117","child":null},{"name":"密云区","id":"110118","child":null},{"name":"延庆区","id":"110119","child":null}]
    },
    {"name":"北京市","id":"110000","child":null}
   ]
  },
  {
   "name":"河北省","id":"130000","child":[
    {"name":"石家庄市","id":"130100","child":[
     {"name":"市辖区","id":"130101","child":null},{"name":"长安区","id":"130102","child":null},{"name":"桥西区","id":"130104","child":null},{"name":"新华区","id":"130105","child":null},{"name":"井陉矿区","id":"130107","child":null},{"name":"裕华区","id":"130108","child":null},{"name":"藁城区","id":"130109","child":null},{"name":"鹿泉区","id":"130110","child":null},{"name":"栾城区","id":"130111","child":null},{"name":"井陉县","id":"130121","child":null},{"name":"正定县","id":"130123","child":null},{"name":"行唐县","id":"130125","child":null},{"name":"灵寿县","id":"130126","child":null},{"name":"高邑县","id":"130127","child":null},{"name":"深泽县","id":"130128","child":null},{"name":"赞皇县","id":"130129","child":null},{"name":"无极县","id":"130130","child":null},{"name":"平山县","id":"130131","child":null},{"name":"元氏县","id":"130132","child":null},{"name":"赵县","id":"130133","child":null},{"name":"晋州市","id":"130183","child":null},{"name":"新乐市","id":"130184","child":null}]
    },
    {"name":"唐山市","id":"130200","child":[
     {"name":"市辖区","id":"130201","child":null},{"name":"路南区","id":"130202","child":null},{"name":"路北区","id":"130203","child":null},{"name":"古冶区","id":"130204","child":null},{"name":"开平区","id":"130205","child":null},{"name":"丰南区","id":"130207","child":null},{"name":"丰润区","id":"130208","child":null},{"name":"曹妃甸区","id":"130209","child":null},{"name":"滦县","id":"130223","child":null},{"name":"滦南县","id":"130224","child":null},{"name":"乐亭县","id":"130225","child":null},{"name":"迁西县","id":"130227","child":null},{"name":"玉田县","id":"130229","child":null},{"name":"遵化市","id":"130281","child":null},{"name":"迁安市","id":"130283","child":null}]
    },
    {"name":"秦皇岛市","id":"130300","child":[
     {"name":"市辖区","id":"130301","child":null},{"name":"海港区","id":"130302","child":null},{"name":"山海关区","id":"130303","child":null},{"name":"北戴河区","id":"130304","child":null},{"name":"抚宁区","id":"130306","child":null},{"name":"青龙满族自治县","id":"130321","child":null},{"name":"昌黎县","id":"130322","child":null},{"name":"卢龙县","id":"130324","child":null}]
    },
    {"name":"邯郸市","id":"130400","child":[
     {"name":"市辖区","id":"130401","child":null},{"name":"邯山区","id":"130402","child":null},{"name":"丛台区","id":"130403","child":null},{"name":"复兴区","id":"130404","child":null},{"name":"峰峰矿区","id":"130406","child":null},{"name":"邯郸县","id":"130421","child":null},{"name":"临漳县","id":"130423","child":null},{"name":"成安县","id":"130424","child":null},{"name":"大名县","id":"130425","child":null},{"name":"涉县","id":"130426","child":null},{"name":"磁县","id":"130427","child":null},{"name":"肥乡县","id":"130428","child":null},{"name":"永年县","id":"130429","child":null},{"name":"邱县","id":"130430","child":null},{"name":"鸡泽县","id":"130431","child":null},{"name":"广平县","id":"130432","child":null},{"name":"馆陶县","id":"130433","child":null},{"name":"魏县","id":"130434","child":null},{"name":"曲周县","id":"130435","child":null},{"name":"武安市","id":"130481","child":null}]
    }
   ]
  },
  {
   "name":"湖南省","id":"430000","child":[
    {"name":"长沙市","id":"430100","child":[
     {"name":"市辖区","id":"430101","child":null},{"name":"芙蓉区","id":"430102","child":null},{"name":"天心区","id":"430103","child":null},{"name":"岳麓区","id":"430104","child":null},{"name":"开福区","id":"430105","child":null},{"name":"雨花区","id":"430111","child":null},{"name":"望城区","id":"430112","child":null},{"name":"长沙县","id":"430121","child":null},{"name":"宁乡县","id":"430124","child":null},{"name":"浏阳市","id":"430181","child":null}]
    },
    {"name":"株洲市","id":"430200","child":[
     {"name":"市辖区","id":"430201","child":null},{"name":"荷塘区","id":"430202","child":null},{"name":"芦淞区","id":"430203","child":null},{"name":"石峰区","id":"430204","child":null},{"name":"天元区","id":"430211","child":null},{"name":"株洲县","id":"430221","child":null},{"name":"攸县","id":"430223","child":null},{"name":"茶陵县","id":"430224","child":null},{"name":"炎陵县","id":"430225","child":null},{"name":"醴陵市","id":"430281","child":null}]
    },
    {"name":"湘潭市","id":"430300","child":[
     {"name":"市辖区","id":"430301","child":null},{"name":"雨湖区","id":"430302","child":null},{"name":"岳塘区","id":"430304","child":null},{"name":"湘潭县","id":"430321","child":null},{"name":"湘乡市","id":"430381","child":null},{"name":"韶山市","id":"430382","child":null}]
    },
    {"name":"衡阳市","id":"430400","child":[
     {"name":"市辖区","id":"430401","child":null},{"name":"珠晖区","id":"430405","child":null},{"name":"雁峰区","id":"430406","child":null},{"name":"石鼓区","id":"430407","child":null},{"name":"蒸湘区","id":"430408","child":null},{"name":"南岳区","id":"430412","child":null},{"name":"衡阳县","id":"430421","child":null},{"name":"衡南县","id":"430422","child":[
      {"name":"三塘镇",id:"430422",child:null},{"name":"车江镇",id:"430422",child:null}
     ]},{"name":"衡山县","id":"430423","child":null},{"name":"衡东县","id":"430424","child":null},{"name":"祁东县","id":"430426","child":null},{"name":"耒阳市","id":"430481","child":null},{"name":"常宁市","id":"430482","child":null}]
    }
   ]
  },
  {
   "name":"广东省","id":"440000","child":[
    {"name":"广州市","id":"440100","child":[
     {"name":"市辖区","id":"440101","child":null},{"name":"荔湾区","id":"440103","child":null},{"name":"越秀区","id":"440104","child":null},{"name":"海珠区","id":"440105","child":null},{"name":"天河区","id":"440106","child":null},{"name":"白云区","id":"440111","child":null},{"name":"黄埔区","id":"440112","child":null},{"name":"番禺区","id":"440113","child":null},{"name":"花都区","id":"440114","child":null},{"name":"南沙区","id":"440115","child":null},{"name":"从化区","id":"440117","child":null},{"name":"增城区","id":"440118","child":null}]
    },
    {"name":"韶关市","id":"440200","child":[
     {"name":"市辖区","id":"440201","child":null},{"name":"武江区","id":"440203","child":null},{"name":"浈江区","id":"440204","child":null},{"name":"曲江区","id":"440205","child":null},{"name":"始兴县","id":"440222","child":null},{"name":"仁化县","id":"440224","child":null},{"name":"翁源县","id":"440229","child":null},{"name":"乳源瑶族自治县","id":"440232","child":null},{"name":"新丰县","id":"440233","child":null},{"name":"乐昌市","id":"440281","child":null},{"name":"南雄市","id":"440282","child":null}]
    },
    {"name":"深圳市","id":"440300","child":[
     {"name":"市辖区","id":"440301","child":null},{"name":"罗湖区","id":"440303","child":null},{"name":"福田区","id":"440304","child":null},{"name":"南山区","id":"440305","child":null},{"name":"宝安区","id":"440306","child":null},{"name":"龙岗区","id":"440307","child":null},{"name":"盐田区","id":"440308","child":null}]
    },
    {"name":"珠海市","id":"440400","child":[
     {"name":"市辖区","id":"440401","child":null},{"name":"香洲区","id":"440402","child":null},{"name":"斗门区","id":"440403","child":null},{"name":"金湾区","id":"440404","child":null}]
    }
   ]
  },
  {
   "name":"南沙群岛","id":"900001","child":null
  }
 ];

 var eText = document.getElementById('input');
 new CascadeMenu(eText,json);

一个封装好的js级联下拉功能就完成了,可以根据图片自己编写css样式以达到需要的效果。

以上就是js面向对象封装级联下拉菜单列表的实现步骤的详细内容,更多关于js 封装下拉菜单的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
jquery+json 通用三级联动下拉列表
Apr 19 Javascript
JQuery插件Quicksand实现超炫的动画洗牌效果
May 03 Javascript
AngularJS的一些基本样式初窥
Jul 27 Javascript
Javascript技术栈中的四种依赖注入详解
Feb 23 Javascript
Angular多选、全选、批量选择操作实例代码
Mar 10 Javascript
bootstrap suggest下拉框使用详解
Apr 10 Javascript
Vue 兄弟组件通信的方法(不使用Vuex)
Oct 26 Javascript
react 父子组件之间通讯props
Sep 08 Javascript
使用apifm-wxapi模块中的问题及解决方法
Aug 05 Javascript
解决layui弹出层layer的area过大被遮挡的问题
Sep 21 Javascript
vue 路由懒加载中给 Webpack Chunks 命名的方法
Apr 24 Javascript
基于VUE实现判断设备是PC还是移动端
Jul 03 Javascript
JavaScript实现点击出现子菜单效果
Feb 08 #Javascript
深入理解javascript中的this
Feb 08 #Javascript
Vue中使用wangeditor富文本编辑的问题
Feb 07 #Vue.js
vue使用lodop打印控件实现浏览器兼容打印的方法
Feb 07 #Vue.js
js基于canvas实现时钟组件
Feb 07 #Javascript
nestjs中异常过滤器Exceptionfilter的具体使用
Feb 07 #Javascript
js实现类选择器和name属性选择器的示例步骤
Feb 07 #Javascript
You might like
PHP实现把文本中的URL转换为链接的auolink()函数分享
2014/07/29 PHP
分享php邮件管理器源码
2016/01/06 PHP
php 调用ffmpeg获取视频信息的简单实现
2017/04/03 PHP
PHP Ajax跨域问题解决方案代码实例
2020/08/01 PHP
JavaScript面向对象之体会[总结]
2008/11/13 Javascript
IE8 中使用加速器(Activities)
2010/05/14 Javascript
读取input:file的路径并显示本地图片的方法
2013/09/23 Javascript
jquery、js操作checkbox全选反选
2014/03/12 Javascript
基于Jquery代码实现手风琴菜单
2015/11/19 Javascript
jquery对象和DOM对象的任意相互转换
2016/02/21 Javascript
JavaScript入门系列之知识点总结
2016/03/24 Javascript
简单讲解jQuery中的子元素过滤选择器
2016/04/18 Javascript
基于iscroll.js实现下拉刷新和上拉加载效果
2016/11/28 Javascript
JS实现监控微信小程序的原理
2018/06/15 Javascript
Vue extend的基本用法(实例详解)
2019/12/09 Javascript
python读文件逐行处理的示例代码分享
2013/12/27 Python
python中的字典使用分享
2016/07/31 Python
Python极简代码实现杨辉三角示例代码
2016/11/15 Python
深入理解NumPy简明教程---数组3(组合)
2016/12/17 Python
Python连接数据库学习之DB-API详解
2017/02/07 Python
keras的backend 设置 tensorflow,theano操作
2020/06/30 Python
伯克斯奥特莱斯:Burkes Outlet
2019/03/30 全球购物
全球高级音频和视频专家:HiDef Lifestyle
2019/08/02 全球购物
全球领先的全景影像品牌:Insta360
2019/08/21 全球购物
给面试官的感谢信
2014/02/01 职场文书
美术教师自我鉴定
2014/02/12 职场文书
幼儿园元旦家长感言
2014/02/27 职场文书
销售职业生涯规划范文
2014/03/14 职场文书
优秀语文教师事迹
2014/05/18 职场文书
森林防火标语
2014/06/23 职场文书
机械工程及其自动化专业求职信
2014/08/08 职场文书
大学毕业谢师宴致辞
2015/07/27 职场文书
学会Python数据可视化必须尝试这7个库
2021/06/16 Python
使用feign服务调用添加Header参数
2021/06/23 Java/Android
关于Oracle12C默认用户名system密码不正确的解决方案
2021/10/16 Oracle
MySQL创建管理HASH分区
2022/04/13 MySQL