本章节将讲述怎么利用js实现具有联动和搜索功能的combobox组件。界面展示效果图如下方所示,图1为控件初始状态,图2为点击下拉选择框显示的效果。图1两个combobox互为联动效果,下方将介绍如何实现联动效果。
图1 控件初始化
图2 下拉展示图
图2中展示的下拉选择信息框为多维数据,具体根据传递的数据源创建。这边的数据源需要按照js中指定的格式,不过您可以根据您的项目需求对js的数据获取进行修改。json返回数据实例如下:
{
"Code": "1",
"Msg": "获取成功",
"Data": [{
"ID": "10d037aca9a180d",
"Name": "02.克拉",
"ParentId": "-1",
"IsSelect": false,
"SubMenu": [{
"ID": "d23aef68962fc0ca",
"Name": "01.前台",
"ParentId": "10d037aca9a180d",
"IsSelect": true,
"SubMenu": ""
}]
}]
}
我们创建combobox的设计思路分为两部分,第一部分创建文本选择框,由三个div组成,设置position属性为:relative,这样可以将选择框放在指定位置;第二部分为下来选择信息框,这部分由搜索输入框和内容显示ul组成。具体创建的代码如下:
//#region 创建下拉框
var _SelectClass = new Date().getTime();
_SelectClass = 'S' + _SelectClass;
var _Select = $('' +
'<div class="selectinf ' + _SelectClass + '" data-open=0>\n' +
' <div class="text-contents"></div>\n' +
' <div class="down-up down"></div>\n' +
'</div>');
element.append(_Select);
var _ComboboxClass = new Date().getTime();
_ComboboxClass = 'C' + _ComboboxClass;
var _Combobox = $('' +
'<div class="combobox ' + _ComboboxClass + '">\n' +
' <div class="search">\n' +
' <input type="text" class="search-text" placeholder="输入关键词搜索"/>\n' +
' <span class="fa fa-search search-btn"></span>\n' +
' </div>\n' +
' <div class="main"><ul class="navMenu"></ul></div>\n' +
'</div>');
$("body").append(_Combobox);
self.returnclass(_SelectClass, _ComboboxClass);
//#endregion
上方代码中除了公共的class外,同时采用时间戳作为class用于区分唯一性,_ComboboxClass对象直接创建在body标签下,应为这边的position属性为absolute,方便点击文本选择框或下拉按钮时将其显示在下方区域。在此我们需要对搜索栏绑定对应的事件,搜索栏采用的是"input"标签,我们对其绑定了获得焦点和没有获得焦点事件、文本输入改变以及键盘点击事件,相对应的写法如下代码所示:
//#region 输入框绑定焦点事件
$("." + _ComboboxClass).delegate("input", "blur", function () {
$(this).parent().css("border", "1px silver solid");
});
$("." + _ComboboxClass).delegate("input", "focus", function () {
$(this).parent().css("border", "1px #0B7CED solid");
});
//#endregion
//#region 输入框绑定键盘事件及文本改变事件
$("." + _ComboboxClass).delegate("input", "input", function () {//文本输入框
if ($(this).val() == "") {
$('.' + _ComboboxClass + ' .main .navMenu').find("li").css("display", "block");
}
});
$("." + _ComboboxClass).delegate("span", "click", function () {//搜索按钮
var e = jQuery.Event("keypress");//模拟一个键盘事件
e.keyCode = 13;//keyCode=13是回车
$("." + _ComboboxClass + " .search .search-text").trigger(e);
});
$("." + _ComboboxClass).delegate("input", "keypress", function (event) {
if (event.keyCode == "13") {
//todo:执行搜索操作
}
})
//#endregion
当用户输入的字符为空时,我们不必要执行搜索含有相关字符的li元素,我们这边的搜索是对数据源进行模糊搜索,同时不能区分大小写,那么我们输入的字符应该转换成小写或者大写,本功能转换的是小写,转换成小写字符的代码为:value.toLowerCase();
由于我们的数据源是具有上下级关系的,因此我们匹配到含有相关字符的数组集合需要再次变成树形结构数组,方便我们二次创建ul-li元素。查找含有指定字符数组和获取需要层级关系数组功能方法函数如下所示:
//#region 查找含有指定字符的数组
/**
* 查找含有指定字符的数组
* @param txt 搜索文本
* @param oldarr 数据源
* @return {Array} 返回含有指定字符的数组
* @constructor
*/
ProcessingArray = function (txt, oldarr) {
var newarr = [];
oldarr.map(function (item) {
if (item.Name.toLowerCase().indexOf(txt) >= 0) {
var obj = {
ID: item.ID,
ParentId: item.ParentId,
Name: item.Name,
IsSelect: item.IsSelect,
}
newarr.push(obj);
}
});
return newarr;
}
//#endregion
//#region 获取需要层级关系数组
/**
* 获取需要层级关系数组
* @param oldarr 旧数据源
* @param pid 父节点Id
* @constructor
*/
SelectParentArray = function (oldarr, pid) {
$(oldarr).each(function (index, item) {
if (item.ID == pid) {
var IsContinue = true;
if (_ParentArray.length > 0) {
$(_ParentArray).each(function (_index, _item) {
if (_item.ID == item.ID) {
IsContinue = false;
return false;//退出循环
}
})
}
if (!IsContinue) {
return true;//退出本次循环,继续执行下一个循环
}
var obj = {
ID: item.ID,
ParentId: item.ParentId,
Name: item.Name,
IsSelect: item.IsSelect,
}
_ParentArray.push(obj);
SelectParentArray(oldarr, item.ParentId);
}
})
}
//#endregion
注意:我们这边的查询对象方法如果是用map的话,需要考虑到递归操作,尽可能的把含有相关的数据查询出来。
我们对数据源进行二次操作后,接下来就是创建ul-li元素值,由于为了实现外部联动的效果,我们这边的ul-li的创建采用extend。由于创建li的文本不具有规律性,所以我们这边需要计算文字所需要占的宽,然后设置整理ul容器的宽度,尽量使得界面ul能够美观。计算文字所占的宽度代码如下:
/**
* js获取文本显示宽度
* @param str: 文本
* @return 文本显示宽度
*/
getTextWidth: function (str) {
var w = $('body').append($('<span stlye="display:none;" id="textWidth"/>')).find('#textWidth').html(str).width();
$('#textWidth').remove();
return w;
}
创建li元素的完整代码如下:
$.extend({
/**
* 下拉框信息创建
* @param arr 信息集合数组
* @param ddbclass 下拉框信息组集合class
* @param cbbclass 下拉框class
* @param callback 回调函数
* @param element 父节点元素集 例如$(this)
* @constructor
*/
DropDownBox: function (arr, ddbclass, cbbclass, callback, element) {
$("." + cbbclass + ' .text-contents').text("");
$("." + cbbclass + ' .text-contents').data("Id", "");
$("." + ddbclass + " .search .search-text").text("");
var width = element.outerWidth() - 8;
if ($(arr).length == 0) {
$('.' + ddbclass).css("width", width);
return;
}
CachArry = [];
//#region 创建ul-li
$('.' + ddbclass + ' .main .navMenu').empty();
var textwidth = 0;//计算文字所需要占的宽度
$(arr).each(function (index, item) {
var obj = {
ID: item.ID,
ParentId: item.ParentId,
Name: item.Name,
IsSelect: item.IsSelect,
}
CachArry.push(obj);
var _width = $.getTextWidth(item.Name);//获取文字的宽度
textwidth = textwidth >= _width ? textwidth : _width;//判断宽度最大
var url = item.url || "javascript:;";
var lihtml = $("<li class='" + item.ID + "' data-id='" + item.ID + "' data-parentId='" + item.ParentId + "' data-select='" + item.IsSelect + "'><a href='" + url + "' target='_blank' class='inactive'><span class='title'>" +
item.Name + "</span></a></li>");
if (item.SubMenu && item.SubMenu.length > 0) {
$(lihtml).children('a').append("<span class='arrow'></span>");
CreateSubMenu(item.SubMenu, lihtml, textwidth, CachArry);
}
$('.' + ddbclass + ' .main .navMenu').append(lihtml);
});
//#endregion
//#region ul-li点击事件绑定
$('.' + ddbclass + ' .main .navMenu').undelegate('li a', 'click');
$('.' + ddbclass + ' .main .navMenu').delegate('li a', 'click', function (e) {
e.stopPropagation();//禁止向上冒泡
e.preventDefault();//系统不执行跳转
var isselect = $(this).parent().data("select");//获取数据项是否可以选择
if (isselect) {
$("." + cbbclass + ' .text-contents').text($(this)[0].text);
$("." + cbbclass + ' .text-contents').data("Id", $(this).parent().data("id"));
callback($(this).parent().data("id"));
}
var parent = $(this).parent().parent(); //获取当前页签的父级的父级
var labeul = $(this).parent("li").find(">ul");
if ($(this).parent().hasClass('open') == false) {
//展开未展开
parent.find('ul').slideUp(300);
parent.find("li").removeClass("open");
parent.find('li a').removeClass("active").find(".arrow").removeClass("open")
$(this).parent("li").addClass("open").find(labeul).slideDown(300);
$(this).addClass("active").find(".arrow").addClass("open");
} else {
$(this).parent("li").removeClass("open").find(labeul).slideUp(300);
if ($(this).parent().find("ul").length > 0) {
$(this).removeClass("active").find(".arrow").removeClass("open")
} else {
$(this).addClass("active")
}
}
});
//#endregion
//#region 设置宽度
textwidth += 45;
$('.' + ddbclass).css("width", textwidth >= width ? textwidth : width);
//#endregion
}
})
由于我们不清楚传递过来的数据需要具有下一级元素,因此我们需要对item的SubMenu值进行判断,如果当前的值长度大于零,那么直接调用创建子级元素ul-li。上方展示的代码创建的ul-li只有一级,二级及以上的ul-li创建则由CreateSubMenu方法执行,这边同样判断是否含有下一级数据,如果有则递归调用本身方法,以此类推直到所有数据源创建完。
创建二级及以上ul-li的实现代码如下:
//#region 创建子集节点
/**
* 创建子集节点
* @param submenu 子集元素集合
* @param lihtml li项
* @param textwidth 计算宽度
* @param array 缓存数组
* @constructor
*/
CreateSubMenu = function (submenu, lihtml, textwidth, array) {
var subUl = $("<ul class='sub-menu' style='display: none'></ul>"),
callee = arguments.callee,
subLi;
$(submenu).each(function (index, item) {
var obj = {
ID: item.ID,
ParentId: item.ParentId,
Name: item.Name,
IsSelect: item.IsSelect,
}
array.push(obj);
var _width = $.getTextWidth(item.Name);//获取文字的宽度
textwidth = textwidth >= _width ? textwidth : _width;//判断宽度最大
var url = item.url || "javascript:;";
subLi = $("<li class='" + item.ID + "' data-id='" + item.ID + "' data-parentId='" + item.ParentId + "' data-select='" + item.IsSelect + "'><a href='" + url + "' target='_blank'><span class='title'>" + item.Name + "</span></a></li>");
if (item.SubMenu && item.SubMenu.length > 0) {
$(subLi).children('a').append("<span class='arrow'></span>");
callee(item.SubMenu, subLi, textwidth, array);
}
$(subUl).append(subLi);
});
$(lihtml).append(subUl);
}
//#endregion
讲完这些,我们还需创建一个构造函数,我们需要对定义的参数进行赋值。我们这边定义了一些参数,届时您可以根据您的项目需求对参数进行修改或新增以符合您的实际需求。定义的参数类型如下代码:
/**
* 构造函数
* @param element 元素 例如传 $(this)
* @param options 传递参数
* @constructor
*/
var ComboBox = function (element, options) {
this.element = element;
//数据源数据
this.arr = options.arr;
//回调函数
this.callback = options.callback || function () {
};
//返回创建的class,方便再次数据动态创建使用
this.returnclass = options.returnclass || function () {
};
//创建
this.CreateComboBox(this.arr, this.element);
};
代码中,我们定义了两个回调函数,第一回调函数是回调点击对象的Class,一个回调的是下拉框class、下拉框信息组集合class,可以通过第二个回调对象实现联动效果。
调用实例代码:
$.ajax({
type: "GET",
url: url,
async: true,
success: function (result) {
var res = eval(result);
if (res["Code"] == 1) {
var json = res["Data"];
if (json != undefined && json.length > 0) {
var _cbbclass, _ddbclass;
$(".department").ComboBox({
arr: json,
callback: function (id) {//回调函数 每个节点I数据源对应表Id
$.ajax({
url: url,
type: "GET",
async: true,
success: function (res) {
var _res = eval(res);
if (_res["Code"] == 1) {
var _json = _res["Data"];
$.DropDownBox(_json, _ddbclass, _cbbclass, function (_id) {
}, $(".username"))
}
}
})
},
returnclass: function (cbbclass, ddbclass) {//下拉框class、下拉框信息组集合class
}
})
$(".username").ComboBox({
arr: null,
callback: function (id) {//回调函数
console.log(id);
},
returnclass: function (cbbclass, ddbclass) {//下拉框class、下拉框信息组集合class
_cbbclass = cbbclass;
_ddbclass = ddbclass;
}
})
}
}
},
//#region 数据请求错误
error: function (XMLHttpRequest, textStatus, errorThrown) {
$.confirm({
title: "错误提示",
content: "数据读取失败,具体信息为=>状态码:" + XMLHttpRequest.status + " 状态:" + XMLHttpRequest.readyState + " 错误信息:" + textStatus + "点击[确定]再次请求数据",
closeIcon: true,
type: 'orange',//样式设置 orange blue purple dark
typeAnimated: true,//动画效果
dragWindowGap: 0,
buttons: {
ok: {
text: '确定',
btnClass: 'btn-blue',
action: function () {
location.reload();//重新加载数据
}
}
}
});
}
//#endregion
});
到目前为止我们创建combobox的操作流程已经介绍完毕,接下来是一些细节上的注意事项处理,比如点击非指定对象时需要对我们的下拉信息框进行隐藏操作、创建缓存数据方便元素操作。