本教程讲解的级联下拉菜单是根据已有json数据创建的DOM元素。点击文本框后,显示一级菜单。如果菜单中包含子菜单,菜单右侧会有指示箭头。点击菜单之后,会再显示下一级菜单,以此类推。当菜单下无子菜单时,选择菜单后会在文本框中显示。
打开后的级联菜单效果如图所示:
使用封装好的插件,只需要有一个input元素,即可通过插件自动生成级联下拉菜单,html代码如下所示:
<div style="margin-top:100px;text-align:center;">
<input type="text" id="input">
</div>
样式可以根据图片上的效果自己编写,也可以从 《使用面向对象的方式封装js级联下拉菜单列表的实例教程》源码 中复制。
接下来看下具体封装的js代码怎么实现。
- 声明级联菜单的构造函数
构造函数需要传入一个文本框元素和菜单关联数据两个参数。
//elem为文本框,data为菜单关联数据
function CascadeMenu(elem,data){
}
- 在构造函数中创建级联菜单相关元素,并放到页面中,具体代码如下:
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;
}
- 在文本框上绑定点击事件,生成级联下拉菜单
刚才已经把需要的元素都放到了页面中,现在可以通过点击文本框显示和隐藏级联菜单元素;
在显示级联菜单元素时,应该要通过数据生成级联下拉菜单。
因为每次点击都需要生成,所以可以在构造函数的原型上添加一个方法。如下所示:
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);
}
- 菜单上绑定事件,用于选择菜单
级联菜单有两种类型,一种是有下级菜单的,点击时显示下级菜单;
一种是没有下级菜单的,点击时直接选择菜单并在文本框中按级别显示所选择的菜单。代码如下所示:
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();
}
});
}
- 在页面空白处点击时,隐藏菜单
现在只能在文本框上点击显示和隐藏菜单。一般来说任何打开的弹框,都希望在弹框以外的位置可以关闭掉。这样需要修改一下文本框上的点击事件函数:当打开菜单时,要在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