转载自:文本域 自定义下拉框 支持模糊检索 关键字高亮 上下选择
一、需求
需要创建一个常见问题库,填写存在问题时可以下拉选择,可以模糊搜索,也可以手写。如果选择了问题库中的内容,自动填充内容到存在问题中,并且自动选择严重程度,最终保存的值还是界面显示的内容。
二、可行性研究
1、h5的datalist 可以支持下拉选择,模糊搜索,手写 。经过试验 list属性只能用于input输入框 不可适用于textarea。 无法直接使用datalist方式,pass。
2、使用input的datalist和textarea结合的方式,界面显示textarea。点击变成input使用datalist方式进行检索,填写完成后转成textarea。 经过试验,功能可以达到,但是input不换行,用户体验太差,pass。
3、使用semantic ui的输入框 也不支持textarea,pass。
4、手写下拉框,可行。
三、实现
1、实现思路
a、点击文本框,弹出下拉框。下拉框以td绝对定位,根据页面高度计算位置,下拉框是在td上面还是下面
b、点击空白处关闭下拉框
c、文本域输入内容,检索下拉框匹配内容,高亮显示关键字,隐藏没有匹配项内容
d、监听键盘上下键,动态选择下拉框内容,并自动填充值
2、在页面创建临时div 默认隐藏 用于缓存数据
JavaScript
<div id="tempQuestionDiv" style="display:none">
<ul>
<#list problemList as list>
<li><span severityLevel="${list.severityLevel}">${list.questionContent}</span><dd>${list.severityLevelName}</dd></li>
</#list>
</ul>
</div>
3、页面结构
td中一个textarea
4、界面样式
CSS
.deductCheckInfo{
position: relative;
}
#questionDiv{
border: 1px solid #96C8DA;
width: 321px;
height: 300px;
z-index: 9999;
position: absolute;
text-align:left;
background: white;
border-radius:5px;
padding: 5px 6px;
overflow-y: scroll;
}
#questionDiv ul li{
padding:3px 6px;
cursor: pointer;
}
#questionDiv ul li span{
margin-bottom: 5px;
color: #333;
font-size: 13px;
display: block;
line-height: 18px;
}
#questionDiv ul li dd{
color: #333;
font-size: 13px;
display: block;
opacity: 0.7 ;
}
#questionDiv ul .active{
background: rgba(0, 0, 0, 0.07);
/* font-weight: 600; */
}
#questionDiv ul li:hover{
background: rgba(0, 0, 0, 0.14);
}
5、事件绑定和提取方法
a、文本域点击事件
JavaScript
//输入框点击事件
$("body").on("click",".deductCheckInfo textarea",function(event){
var that=this;
//先删除已有的下拉框
$("#questionDiv").remove();
//重新获取下拉框内容
var tempQuestionDiv=$("#tempQuestionDiv").html();
//判断当前td下是否存在下拉框
var $QuestionDiv=$(this).parent().find("#questionDiv");
//当前td下不存在下拉框
if($QuestionDiv && $QuestionDiv.length==0){
//添加下拉框
$(this).after("<div id=\"questionDiv\">"+tempQuestionDiv+"</div>");
//获取下拉框的高度 获取包括padding的高度加上边框线高度
var questionDivHeight=$("#questionDiv").innerHeight()+2;
//获取td的高度
var deductCheckInfoTdHeight=$(this).parent().innerHeight();
//获取td的Y坐标
var tdY=$(this).parent().offset().top;
//获取页面高度(下边距60+form表单高度+form表单距离顶部的高度)
var formHeight=60+$("div.form").innerHeight()+$("div.form").offset().top;
//高度差 td左下角位置离页面底部的距离(页面高度-td的坐标-td的高度)
var tempHeight=formHeight-tdY-deductCheckInfoTdHeight;
//TD距离页面底部的距离小于下拉框高度时 且 (td的Y减去tab项的高度)大于下拉框高度时
if(tempHeight<questionDivHeight && (tdY-$("#checkImplementTab").height())>questionDivHeight){
//将下拉框向上移动 避免遮挡
var top=-(questionDivHeight);
//移动
$("#questionDiv").css("top",top)
}
//点击事件
$("#questionDiv").find("ul li").click(function(){
//获取li的jq对象
var $li=$(this);
//获取textarea的jq对象
var $textarea=$(that);
//下拉框回写数据
selectProblemData($li,$textarea);
});
}
//获取当前文本域内容
var textareaVal=$(this).val();
//下拉框内容检索和显示
selectProblemShow(textareaVal);
//取消冒泡
event.stopPropagation();
});
b、文本框输入内容
JavaScript
//监听文本框输入内容
$(".deductCheckInfo textarea").on("input propertychange",function(e){
//获取当前输入内容
var curText=$(this).val();
//下拉框内容检索和显示
selectProblemShow(curText);
});
c、监听键盘按键
JavaScript
//监听文本框键盘按键
$(".deductCheckInfo textarea").on("keydown",function(e){
var keyCode=e.keyCode;
//判断下拉框显示的时候 并且只有 上下键 才触发
if(!$("#questionDiv").is(":hidden") && (keyCode==38 || keyCode==40)){
//需要改为显示的全部li
var thisliall=$("#questionDiv ul li");
//显示的全部li
var thisli=new Array();
thisliall.each(function(){
if(!$(this).is(":hidden")){
thisli.push($(this));
}
});
//当前选中的li标签
var activeLi=$("#questionDiv ul li[class='active']");
//文本域jq对象
var $textarea=$(this);
//下一个需要选中的li
var $nextLi;
//下一行是否是第一行
var firstFlag=false;
//上一行是否是最后一行
var lastFlag=false;
//如果没有选中的数据
if(activeLi && activeLi.length==0){
//向上 添加最后一行作为下一个需要选中的li
if(keyCode==38){
$nextLi=$(thisli[thisli.length-1]);
lastFlag=true;
//向上滚动
srollUpFun($nextLi,lastFlag);
//向下 添加第一行作为下一个需要选中的li
}else if(keyCode==40){
$nextLi=$(thisli[0]);
firstFlag=true;
//向下滚动
srollDownFun($nextLi,firstFlag);
}
//如果有选中的数据
}else{
//向上 递归选中上一行
if(keyCode==38){
$nextLi=getPrevShowLi(activeLi);
//如果没有上一行,则设置第一行为下一行
if($nextLi && $nextLi.length==0){
$nextLi=$(thisli[thisli.length-1]);
lastFlag=true;
}
//向上滚动
srollUpFun($nextLi,lastFlag);
//向下 递归选中下一行
}else if(keyCode==40){
$nextLi=getNextShowLi(activeLi);
//如果没有下一行,则设置第一行为下一行
if($nextLi && $nextLi.length==0){
$nextLi=$(thisli[0]);
firstFlag=true;
}
//向下滚动
srollDownFun($nextLi,firstFlag);
}
}
//清除当前选中
activeLi.removeClass("active");
//下一个li添加选中
$nextLi.addClass("active");
//下拉框回写数据
selectProblemData($nextLi,$textarea);
}
});
d、空白处点击事件
JavaScript
//点击空白处隐藏删除下拉框
$(document).click(function(e){
$("#questionDiv").remove();
});
e、提取方法
JavaScript
/**
根据输入值 显示问题项目 下拉框显示效果
*/
function selectProblemShow(inputVal){
//先显示下拉框
$("#questionDiv").show();
//获取全部数据
var li=$("#questionDiv").find("ul li");
//没有数据时,不显示下拉框
if(li && li.length==0){
$("#questionDiv").hide();
}
//li的总数
var totalli=li.length;
//隐藏li的总数
var hiddenLiCount=0;
//遍历li
li.each(function(){
//问题内容
var questionContent=$(this).find("span").html();
//去除高亮标签
re1 = new RegExp("<b>","g");
re2 = new RegExp("</b>","g");
//获取原始内容
if(questionContent){
questionContent=questionContent.replace(re1,'').replace(re2,'')
}
//重新设置原始内容
$(this).find("span").html(questionContent);
//该项内容不包含输入的数据,隐藏该项
if(questionContent.indexOf(inputVal)==-1){
$(this).hide();
}else{
//该项内容包含输入的数据,显示该项
$(this).show();
//输入内容不为空时
if(inputVal){
//高亮显示
var values=questionContent.split(inputVal);
//填充数据
$(this).find("span").html(values.join("<b>"+inputVal+"</b>"));
}
}
//如果元素隐藏,计数
if($(this).is(':hidden')){
hiddenLiCount++;
}
});
//全部隐藏,隐藏下拉框
if(totalli==hiddenLiCount){
$("#questionDiv").hide();
}
}
/**
下拉框回写数据
*/
function selectProblemData($li,$textarea){
//获取问题内容
var questionContent=$li.find("span").html();
re1 = new RegExp("<b>","g");
re2 = new RegExp("</b>","g");
//处理高亮标签
if(questionContent){
questionContent=questionContent.replace(re1,'').replace(re2,'');
}
//严重程度
var severityLevel=$li.find("span").attr("severityLevel");
//设置内容
$textarea.val(questionContent);
//设置严重程度
$textarea.parent().next().find("div.dropdown").dropdown('set selected',severityLevel);
$("textArea").autoTextarea({
minHeight:28,
maxHeight:220,//文本框是否自动撑高,默认:null,不自动撑高;如果自动撑高必须输入数值,该值作为文本框自动撑高的最大高度
});
}
/**
递归获取下一个显示的li
*/
function getNextShowLi($li){
//获取下一个元素
var next=$li.next();
//判断是隐藏
if(next.is(":hidden")){
//继续获取下一个元素
next=getNextShowLi(next);
}
return next;
}
/**
递归获取上一个显示的li
*/
function getPrevShowLi($li){
//获取上一个元素
var prev=$li.prev();
//判断是隐藏
if(prev.is(":hidden")){
//继续获取上一个元素
prev=getPrevShowLi(prev);
}
return prev;
}
/**
下拉框 向下选择 选择li超过可见区域时,滚动到li的位置
*/
function srollDownFun($li,firstFlag){
//下拉框容器
var container = $('#questionDiv');
//下一行不是第一行,计算向下滚动
if(!firstFlag){
//获取可见区域li的绝对高度
var tempTop=$li.offset().top - container.offset().top;
//获取下拉框的高度
var questionDivHeight=$("#questionDiv").innerHeight()+2;
//判断可见区域最后一个li的绝对高度(包括本身的高度,取li的左下角的位置)大于下拉框高度
if(tempTop+$li.height()>questionDivHeight){
//滚动 -5(padding)
container.animate({
scrollTop: $li.offset().top - container.offset().top + container.scrollTop()-5
},0);
}
}else{
//是第一行,重置滚动条位置
container.animate({
scrollTop: 0
},0);
}
}
/**
下拉框 向上选择 选择li超过可见区域时,滚动到li的位置
*/
function srollUpFun($li,lastFlag){
//下拉框容器
var container = $('#questionDiv');
//上一行不是最后一行,计算向上滚动
if(!lastFlag){
//下一个需要显示的li绝对高度 li的Y坐标-容器的Y坐标
var tempTop=$li.offset().top - container.offset().top;
//获取下拉框的高度
var questionDivHeight=$("#questionDiv").innerHeight()+2;
//当绝对高度小于0时 说明被遮挡了
if(tempTop<0){
//滚动 滚动条的高度减去 负的绝对高度 -5(padding)
container.animate({
scrollTop: container.scrollTop()-(-tempTop)-5
},0);
}
}else{
//上一行是最后一行,滚动条滚动到底部
var scrollHeight = $('#questionDiv').prop("scrollHeight");
//滚动
container.animate({
scrollTop: scrollHeight
},0);
}
}
四、效果
1、点击效果
2、模糊检索,高亮效果
3、方向键下 选择第一条效果
4、选择完成效果
5、点击回显效果