一.封装成js
;(function($) {
var tNum = 0;
var InputSelect = function($element, options) {
tNum ++;
this.sNum = tNum;
this.$element = $element;
options = options || {};
this.defaults = {
valueId : "",//下拉选中记录后,key值存放在哪,不能为空,$(#valueId).val()... valueId指的是dom元素的id
dataKey : "",//点击选项后返回的值(key),用于保存到数据库等,不能为空,这个dataKey指的是字段名
dataValue:"",//用于点击选项后返回的显示值(Value),不能为空,这个dataValue指的是字段名
//默认本地缓存记录数,越大时相对的查询性能越高,但最好不要太高,
//不然就是一次性加载太多影响界面响应,默认200,可重写
localStoreNum : 200,
showNum : 10,//下拉列表最多显示多少条记录,默认10,可重写
localData:[],//本地缓存数据,一次性加载时使用,json数组
localSearchValue:[],//用于本地查询,支持多个字段,可空,不传时只查询dataValue的字段
dataTitle:[],//用于显示,可多个,可空,不传时默认只显示dataValue的字段
dataMore:[],//用于选中记录时回调函数需要更多信息时用到,可空
remote : false,//是否远程,当查询数据很大时,建议使用远程方式,可空,为空时表示一次性加载
remoteUrl : "",//远程获取数据的url,返回值为json数组字符串,返回值格式 data:[{},{}]
//url里面的参数值是动态值时,传入json对象数组,多个时用逗号分隔 [{},{}]
//对象的key是要传到后台的参数名,值是具体要动态获取值的id字符串
remoteDynParam :[],
//一般不用传,插件css样式默认值是20000,当两个下拉选择在同一列时,
//会出现上面的下拉选择盖不住下面的,这时就要把上面的下拉选择的z-index比20000大
zIndex : null,
width : "100%",//用于显示列表宽度,默认100%
showAllBtn : false,//是否可全选按钮显示
callback : null//点击选项后的回调函数,用于更多赋值操作,结合dataMore使用
};
this.options = $.extend({}, this.defaults, options);
this.sysParam = {
initData : "",//初始化时的数据copy备份
noMoreData : false ,//数据库是否还有更多的数据
lastSearchText : "",//最后一次查询的内容
lastRemoteSearchText : "",//最后一次远程查询的内容
isEnterChoose : false, //是否通过回车键选中选项
isEnterChange : false, //是否通过回车键选中选项后引起的输入框change事件
isClickClean : true,//点击界面任意地方时,默认都会判断隐藏域是否有值,没有就清空显示值
isReSetClickClean : true//setValueClean(false)时,设置后是否只生效一次,默认是,特殊需求可以设为长久有效
}
}
InputSelect.prototype = {
_init:function(){
var opt = this.options;
opt.dataValue = opt.dataValue.toUpperCase();
opt.dataKey = opt.dataKey.toUpperCase();
if(!(opt.localSearchValue instanceof Array)){
console.error("LocalSearchValue must be Array,error id is : "+opt.valueId);
return;
}
if(opt.localSearchValue.length == 0){
opt.localSearchValue.push(opt.dataValue);
}
if(!(opt.dataTitle instanceof Array)){
console.error("DataTitle must be Array,error id is : "+opt.valueId);
return;
}
if(opt.dataTitle.length == 0){
opt.dataTitle.push(opt.dataValue);
}
this.$element.wrap(function() {
var styleStr = "";
if(opt.zIndex){
styleStr = ' style="z-index:'+opt.zIndex+'" ';
}
return '<div class="inputSelect" '+styleStr+'/>';
});
var html = [];
var initHtml = [];
initHtml.push('<ul id="inputUl_'+opt.valueId+'" style="width:'+opt.width+';"');
initHtml.push('>');
if(!opt.remote && opt.localData.length>0){
//构建dom
html = this._buildDom(this._dataKeyFormat(opt.localData),html,false);
}
var self = this;
//远程加载
if(opt.remote){
$.ajax({
type: "POST",
url : opt.remoteUrl,
dataType : 'json',
async:false,
data : this._getAjaxDataParam(),
success: function(result){
var data = result.data;
var dataJsonArray;
//后台如果返回的是json字符串,就转为对象
//如果后台返回的就是json数组对象,就直接使用而不需转换
if(typeof data === "string"){
dataJsonArray = self._parseToJson(data);
}else{
dataJsonArray = data;
}
var dataLen = dataJsonArray.length;
if(dataLen >0){
html = [];
//构建dom
html = self._buildDom(self._dataKeyFormat(dataJsonArray),html,false);
}
},
error: function(){
console.error("Load remote data error!");
}
});
}
initHtml.push(html.join(""));
initHtml.push("</ul>");
//初始化时的数据copy备份
this.sysParam.initData = html.join("");
//构建dom
this.$element.parent().append(initHtml.join(""));
//添加事件处理
this._bindEvent();
},
_getAjaxDataParam : function(){//构建ajax请求的动态参数
var newDataObj = {};
var opt = this.options;
if(opt.remoteDynParam.length > 0){
for(var p=0;p<opt.remoteDynParam.length;p++){
for ( var y in opt.remoteDynParam[p]) {
if(opt.remoteDynParam[p].hasOwnProperty(y)){
newDataObj[y] = $("#"+opt.remoteDynParam[p][y]).val();
}
}
}
}
return newDataObj;
},
_dataKeyFormat : function(dataArr){//考虑到远程返回的json的key有的是大写有的小写,统一转换为大写
var newDataArr = [];
var newData;
var opt = this.options;
var len = dataArr.length;
//远程加载
if(opt.remote){
len = dataArr.length>opt.localStoreNum?opt.localStoreNum:dataArr.length;
}
for (var i = 0; i < len; i++) {
newData = {};
for ( var p in dataArr[i]) {
if(dataArr[i].hasOwnProperty(p)){
newData[p.toUpperCase()] = dataArr[i][p];
}
}
newDataArr[i] = newData;
}
return newDataArr;
},
_nullFormat : function(str){
if(str == null || str == "null"){
str = "";
}
return str;
},
_buildDom : function(arr,html,needHint,searchVal){
var opt = this.options;
var len = arr.length;
//远程加载
if(opt.remote){
len = arr.length>opt.localStoreNum?opt.localStoreNum:arr.length;
}
var dm = opt.dataMore;
var localSearchValueText = [];
if(opt.showAllBtn){
html.push('<li title="全选" style="margin-left:10px;"><input type="button" id="btnAll" value="全选" class="blueButton" onclick="_selectAll()"></li>');
}
for(var i=0;i<len;i++){
localSearchValueText.length = 0;
for(var p=0,pLen=opt.localSearchValue.length;p<pLen;p++){
localSearchValueText.push(this._nullFormat(arr[i][opt.localSearchValue[p].toUpperCase()]));
if(p != pLen-1){
localSearchValueText.push(" ");
}
}
html.push('<li title="'+localSearchValueText.join("")+'"');
if(dm.length > 0){
for(var t=0;t<dm.length;t++){
html.push("data_"+dm[t].toLowerCase()+' ="'+this._nullFormat(arr[i][dm[t].toUpperCase()])+'"');
}
}
if(i>=opt.showNum){
html.push(' style="display:none;"');
}
html.push('>');
html.push('<a data_text="'+this._nullFormat(arr[i][opt.dataValue])+'" data_keyid="'+arr[i][opt.dataKey]+'" >');
var dataValueText = [];
var dataTitleArr = opt.dataTitle;
for(var j=0,jLen=dataTitleArr.length;j<jLen;j++){
dataValueText.push(this._nullFormat(arr[i][dataTitleArr[j].toUpperCase()]));
if(j != jLen-1){
dataValueText.push(" | ");
}
}
if(needHint){
html.push(this._getHintText(dataValueText.join(""),searchVal));
}else{
html.push(dataValueText.join(""));
}
html.push('</a></li>');
}
return html;
},
_select:function(event,$aobj,isEnterChoose){
//var $aobj = $(event.target).closest('a');
if($aobj.length>0){
var keyChange = false;//选项是否有改变
if($("#"+this.options.valueId).val() != $aobj.attr("data_keyid")){
keyChange = true;
}
this.$element.val($aobj.attr("data_text"));
$("#"+this.options.valueId).val($aobj.attr("data_keyid"));
this.$element.next("ul").hide();
if(isEnterChoose){
this.sysParam.isEnterChoose = true;
this.sysParam.isEnterChange = true;
this.sysParam.lastSearchText = $aobj.attr("data_text");
}
//选中后回调
if(this.options.callback){
var dm = this.options.dataMore;
var rtObj = {};
if(dm.length > 0){
for(var t=0;t<dm.length;t++){
rtObj[dm[t]]=$aobj.parent().attr("data_"+dm[t].toLowerCase());
}
}
//把选项是否有改变也作为回调函数的参数
//增加返回当前input框的jquery对象
this.options.callback(rtObj,keyChange,this.$element);
}
}else{
this._preventDefault(event);
}
},
_bindEvent:function(){
var self = this;
//input click事件
this.$element.click(function(event){
$(".inputSelect").find("ul[id!=inputUl_"+self.options.valueId+"]").hide();
self._search(true);
self._stopPropagation(event);
});
//input keyup 事件
this.$element.keyup(function(e){
//输入框的四个箭头按键的keyUp都不应该触发搜索函数
if(e.keyCode != 13 && e.keyCode != 37 && e.keyCode != 39
&& e.keyCode != 38 && e.keyCode != 40
|| (e.keyCode == 13 && !self.sysParam.isEnterChoose)){
self._search(false);
}
if(e.keyCode != 13){
self.sysParam.isEnterChange = false;
}
self.sysParam.isEnterChoose = false;
});
//input keydown 事件
this.$element.keydown(function(event){
//后续可以继续完善通过键盘方向键和回车键快速下拉选择的功能
self._chooseResult(event);
});
//input change
this.$element.change(function(e){
if(!self.sysParam.isEnterChange){
$("#"+self.options.valueId).val("");
}
self.sysParam.isEnterChange = false;
});
//采用事件委托,在ul上执行事件处理即可
this.$element.next("ul").click(function(event){
var $aobj = $(event.target).closest('a');
self._select(event,$aobj,false);
});
//document click 事件 用于隐藏下拉选择点击其他地方时隐藏下拉项
$(document).click(function(){
self.$element.next("ul").hide();
if($("#"+self.options.valueId).val() == "" && self.sysParam.isClickClean){
self.$element.val("");
}
if(!self.sysParam.isClickClean && self.sysParam.isReSetClickClean){
self.sysParam.isClickClean = true;
}
});
//页面卸载时
$(window).unload( function () {
self._domRemove();
} );
},
_chooseResult : function(e){
var ulShow = true;
var $ul = this.$element.next("ul");
if($ul.css("display") == "none"){
ulShow = false;
}
if(ulShow){
var $visibleLi = $ul.children(":visible");
var $allLi = $ul.children();
var $inputSelectChoosed = $visibleLi.closest(".inputSelectChoosed");
var vLen = $visibleLi.length;
var liHeight = $visibleLi.first().height();
var ulMaxHeight = $ul.height();
var liTop = 0;
if(e.keyCode == 40){//向下箭头
if($inputSelectChoosed.length == 0){
$visibleLi.first().addClass("inputSelectChoosed");
if(this._hasScroll($ul)){
$ul.scrollTop(0);//滚动条返回顶部
}
}else{
$allLi.removeClass("inputSelectChoosed");
if($inputSelectChoosed.nextAll(":visible").length == 0){
$visibleLi.first().addClass("inputSelectChoosed");
if(this._hasScroll($ul)){
$ul.scrollTop(0);//滚动条返回顶部
}
}else{
$inputSelectChoosed.nextAll(":visible").first().addClass("inputSelectChoosed");
if(this._hasScroll($ul)){
this._moveScroll($ul,false);
}
}
}
}else if(e.keyCode == 38){//向上箭头
if($inputSelectChoosed.length == 0){
$visibleLi.last().addClass("inputSelectChoosed");
if(this._hasScroll($ul)){
this._moveScroll($ul,false);
}
}else{
$allLi.removeClass("inputSelectChoosed");
if($inputSelectChoosed.prevAll(":visible").length == 0){
$visibleLi.last().addClass("inputSelectChoosed");
if(this._hasScroll($ul)){
this._moveScroll($ul,false);
}
}else{
$inputSelectChoosed.prevAll(":visible").first().addClass("inputSelectChoosed");
if(this._hasScroll($ul)){
this._moveScroll($ul,true);
}
}
}
}else if(e.keyCode == 13){//回车
var $aobj;
if(vLen == 1){
$aobj = $ul.children(":visible").find('a');
this._select(e,$aobj,true);
$allLi.removeClass("inputSelectChoosed");
}else{
if($inputSelectChoosed.length>0){
$aobj = $inputSelectChoosed.find('a');
this._select(e,$aobj,true);
$allLi.removeClass("inputSelectChoosed");
}
}
}else if(e.keyCode == 37 || e.keyCode == 39){//向左箭头/向右箭头
//this._preventDefault(e);
}
}
},
_domRemove:function(){
this.$element.next("ul").remove();
this.$element = null;
this.options = null;
this.sysParam = null;
},
_search : function(clk){
var self = this;
var opt = this.options;
var param = this.sysParam;
//输入框的值
var searchVal = $.trim(this.$element.val());
if(param.lastSearchText != "" && (param.lastSearchText == searchVal && !clk)){
return;
}
if(searchVal == ""){
this.$element.next("ul").empty();
this.$element.next("ul").append(this.sysParam.initData);
//this.$element.next("ul").find("li").hide();
//this.$element.next("ul").find("li:lt("+opt.showNum+")").show();
this.$element.next("ul").slideDown("fast");
param.noMoreData = false;//后台还有
}else{
//不匹配的全部隐藏
//严格来讲这种方式是性能最高的,不用遍历,但可惜受限于特殊字符,只能换成循环
//var $notMatchs = this.$element.next("ul").find("li:not([title*='"+searchVal+"'])");
//$notMatchs.hide();
//匹配的取前10条(若超过10条)
//var $matchs = this.$element.next("ul").find("li[title*='"+searchVal+"']:lt("+opt.showNum+")");
var $allLi = this.$element.next("ul").find("li");
$allLi.removeClass("inputSelectChoosed");
$allLi.hide();
var hintNum = 0;
$.each($allLi,function(i,t){
if(hintNum >= opt.showNum){
return false;
}
//命中
if(self._myIndexOf($(this).attr("title"),searchVal)>-1){
//if($(this).attr("title").indexOf(searchVal)>-1){
hintNum ++;
$(this).children("a").html(self._getHintText($(this).children("a").text(),searchVal));
$(this).show();
}else{
if(opt.showAllBtn){//如果是显示全选20170802hwx修改
if(self._myIndexOf($(this).attr("title"),"全选")>-1){//含“全选”
$(this).show();
}
}else{
$(this).hide();
}
}
});
//没有任何命中隐藏ul 20170802hwx修改
if(hintNum==0){
this.$element.next("ul").hide();
}
//查询命中的条数小于设定的10条(参数),需要去后台加载(除非后台也没有了)
if(opt.remote && hintNum < opt.showNum
&&
(!param.noMoreData || (param.noMoreData
&& self._myIndexOf(searchVal,param.lastRemoteSearchText)== -1)
)){
//数据库加载
var html = [];
var dataParam = this._getAjaxDataParam();
dataParam.inputSearchValue = searchVal;
$.ajax({
type: "POST",
url : opt.remoteUrl,
data : dataParam,
async:false,
dataType : 'json',
contentType:"application/x-www-form-urlencoded;charset=utf-8",
success: function(result){
var data = result.data;
var dataJsonArray;
//后台如果返回的是json字符串,就转为对象
//如果后台返回的就是json数组对象,就直接使用而不需转换
if(typeof data === "string"){
dataJsonArray = self._parseToJson(data);
}else{
dataJsonArray = data;
}
var dataLen = dataJsonArray.length;
if(dataLen > hintNum){//后台返回的比缓存中命中的多时才重新构建
self.$element.next("ul").empty();
//构建dom
html = self._buildDom(self._dataKeyFormat(dataJsonArray),html,true,searchVal);
self.$element.next("ul").append(html.join(""));
self.$element.next("ul").show();
param.noMoreData = false;//后台还有
}else{
param.noMoreData = true;//后台也没有了
param.lastRemoteSearchText = searchVal;
}
},
error: function(){
console.error("Load remote data error!");
}
});
}else{
//$matchs.show();
}
if(hintNum>0){
this.$element.next("ul").slideDown("fast");
}
}
param.lastSearchText = searchVal;
},
_myIndexOf : function(fullText,searchText){
var pos;
if(fullText != null && searchText != null){
pos = fullText.toUpperCase().indexOf(searchText.toUpperCase());
if(pos < 0){
pos = fullText.toLowerCase().indexOf(searchText.toLowerCase());
}
}
return pos;
},
_getHintText : function(aText,searchVal){
var aPos,nbText="",naText,hint;
aPos = this._myIndexOf(aText,searchVal);
nbText = "";
if(aPos>-1){
nbText = aText.substring(0,aPos);
}else{
return aText;
}
hint = "<span class='inputSelectHint'>"+aText.substring(aPos,aPos+searchVal.length)+"</span>";
naText = aText.substring(aPos+searchVal.length);
aText = nbText+hint+naText;
return aText;
},
_stopPropagation : function(e){
var e = e || window.event;
if(e.stopPropagation) { //W3C阻止冒泡方法
e.stopPropagation();
}else{
e.cancelBubble = true; //IE阻止冒泡方法
}
},
_preventDefault : function(e){
var e = e || window.event;
if(e.preventDefault) { //W3C阻止默认行为方法
e.preventDefault();
}else{
e.returnValue = false; //IE阻止默认行为方法
}
},
_parseToJson : function (text) {
try {
return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
} catch (e) {
return false;
}
},
_hasScroll : function($obj){//判断指定元素是否有滚动条
var hasScroll = false;
if($obj.scrollTop()>0){
return true;
}
var height = $obj.height();
var ulMaxHeight = $obj.css("maxHeight");
ulMaxHeight = parseInt(ulMaxHeight);
if(height < ulMaxHeight){
return false;
}
$obj.scrollTop(1);//控制滚动条下移1px
if($obj.scrollTop()>0 ){
hasScroll = true;
}
$obj.scrollTop(0);//滚动条返回顶部
return hasScroll;
},
_moveScroll : function($obj,backTop){
var $visibleLi = $obj.children(":visible");
var prevNum = $visibleLi.closest(".inputSelectChoosed").prevAll(":visible").length;
var liHeight = $visibleLi.first().height();
var ulMaxHeight = $obj.height();
var liTop = prevNum*liHeight;
if(liTop>=ulMaxHeight){
$obj.scrollTop(liTop-ulMaxHeight+ liHeight);//滚动条移动
}else{
if(backTop){
$obj.scrollTop(0);//滚动条返回顶部
}
}
}
}
//重新发送ajax到后台加载数据,searchVal为空时全部加载,不为空时按关键字加载
InputSelect.prototype.reload = function(searchVal,isShow){
var opt = this.options;
var self = this;
var dataParam = this._getAjaxDataParam();
dataParam.inputSearchValue = searchVal;
$.ajax({
type: "POST",
url : opt.remoteUrl,
data : dataParam,
//async:false,
dataType : 'json',
contentType:"application/x-www-form-urlencoded;charset=utf-8",
success: function(result){
var data = result.data;
var dataJsonArray;
/后台如果返回的是json字符串,就转为对象
//如果后台返回的就是json数组对象,就直接使用而不需转换
if(typeof data === "string"){
dataJsonArray = self._parseToJson(data);
}else{
dataJsonArray = data;
}
self.$element.next("ul").empty();
var html = [];
var needHint = false;
if(isShow){
needHint = true;
}
//构建dom
html = self._buildDom(self._dataKeyFormat(dataJsonArray),html,isShow,searchVal);
self.$element.next("ul").append(html.join(""));
if(!isShow){
//重新初始化数据
self.sysParam.initData = html.join("");
}else{
self.$element.next("ul").show();
}
},
error: function(){
console.error("Load remote data error!");
}
});
}
//重新发送ajax到后台加载数据,searchVal为空时全部加载,不为空时按关键字加载,加载完之后自动弹出下拉选择窗
InputSelect.prototype.search = function(searchVal){
this.reload(searchVal,true)
}
//用于设置点击非下拉选择外的任意界面位置时,是否判断已经有选择下拉选项
InputSelect.prototype.setValueClean = function(flag){
this.sysParam.isClickClean = flag;
return this;
}
//setValueClean(false)时,设置后是否只生效一次,默认是,特殊需求可以设为长久有效
InputSelect.prototype.setReSetClickClean = function(flag){
this.sysParam.isReSetClickClean = flag;
return this;
}
// 添加到jquery插件中
$.fn.inputSelect = function(options) {
// 创建对象
var inputSelect = new InputSelect(this, options);
// 调用其方法
inputSelect._init();
//返回对象,方便调用其他API,如重新加载
return inputSelect;
}
})(jQuery);
二.固定加载的使用案例
1).绑定事件
$("#deptNames").inputSelect({
valueId:"typeId",
dataKey : "typeId",
dataValue:"typeName",
width:"200px",
showNum:100,
localData:${ioStoreType}
});
2).jsp页面
<input class="hmcp_list_search_input_mid" type="test" id="deptNames" name="deptNames" maxlength="50" <c:if test="${deptNames!=null }">value="${deptNames}"</c:if>/>
<input type="hidden" id="typeId" name="typeId" value="${typeId }"/>
3).效果图
三.动态加载的使用案例
1).绑定事件
inputSelect = $("#borrowDept").inputSelect({
valueId:"borrowDeptId",
dataKey : "accDeptId",
dataValue:"accDeptName",
localSearchValue:["accDeptName"],
dataTitle:["accDeptName"],
dataMore:["accDeptName"],
zIndex:"90000",
remote:true,
showNum:200,
remoteUrl :"数据访问路径",
remoteDynParam : [{参数:"参数值"}],
callback : ""
});
2).后台处理
Integer accCompId =super.getInt(request, "参数");
String inputSearchValue = super.getString(request, "inputSearchValue");
map.put("data", 封装成json数据);
3).样式
.inputSelect{position: relative;}
.inputSelect ul{margin:0px;padding:0px;padding-left:1px;padding-right:1px;width:auto;height:auto;max-height:240px;overflow:auto;border:1px solid #888888; background-color:#ffffff; position:absolute;right:0px;left:0px;top:24px; z-index:20000; display:none;}
.inputSelect ul li{margin:0;font-size:13px;height:24px; line-height:24px;list-style:none;}
.inputSelect ul li a{display:block;padding-left:10px; padding-right:3px; height:24px; color:#333333; text-decoration:none; cursor: pointer;overflow: hidden;text-overflow:ellipsis;white-space: nowrap;}
.inputSelect ul li a:hover{background-color:#CCC;}
.inputSelectHint{color:red;}
.inputSelectChoosed{background-color:#CCC;}