仿Google的输入下拉提示框
当我们在使用Google或百度搜索引擎时,当你在搜素框中输入一个关键词的前一个或几个字母/汉字,会发现它们会自动把相关的关键词给列出来,如下图所示,非常人性化:
下面,我们就提供了 4 种实现这样效果的方式供您参考:
一、使用RADComboBox控件来实现:(作者:MoFun.CC)
<table>
<tr><td>所属部门</td>
<td><telerik:RadComboBox ID="rcb_MoFun_CC_Department" Runat="server" CssClass="ColumnLeftInput" Width="130px" DataSourceID="test" MarkFirstMatch ="true" DataTextField="department" DataValueField="Id" HighlightTemplatedItems="True" ShowToggleImage="False" Filter="StartsWith" ShowDropDownOnTextboxClick="False" EnableLoadOnDemand="True">
<CollapseAnimation Type="OutQuint" Duration="200"></CollapseAnimation>
</telerik:RadComboBox>
</td>
</tr></table>
二、自己从头实现:(作者:flywe)
2.1、.ASCX控件页面:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Suggest.ascx.cs" Inherits="Suggest" %>
<style type="text/css">
.pnlSuggest
{
border: #000000 1px solid;
background-color: #FFFFFF;
z-index: 9527;
position: absolute;
overflow-y: auto;
overflow-x: hidden;
text-overflow: clip;
}
.pnlSuggest table
{
width: 100%;
}
.pnlSuggest tr
{
width: 100%;
}
.trmouseover
{
width: 100%;
background-color: #397CC3;
}
.trmouseover td
{
text-align: left;
overflow: hidden;
text-overflow: clip;
background-color: #397CC3;
}
.trmouseout
{
width: 100%;
background-color: #FFFFFF;
}
.trmouseout td
{
text-align: left;
overflow: hidden;
text-overflow: clip;
background-color: #FFFFFF;
}
.txtValues
{
display: none;
}
.dataSource
{
display: none;
}
.hiddentd
{
display: none;
}
.hiddenValues
{
display: none;
}
</style>
<script language="javascript" type="text/javascript">
//为string对象添加一个清除前后空格的属性
String.prototype.trim = function()
{
return this.replace(new RegExp("(^[\\s]*)|([\\s]*$)", "g"), "");
};
//显示下拉信息
function ShowSuggest(objInputText)
{
objInputText.onkeyup = ControlSuggest;
objInputText.onblur = RemoveSuggest;
var oldValue = objInputText.parentElement.getElementsByTagName("h6")[0];
//对提示框的控制
function ControlSuggest()
{
var ie = (document.all)?true:false;
if(ie)
{
var keycode = event.keyCode;
var txtvalues = objInputText.value.trim().split(";");
if( !CheckSuggest() && txtvalues[txtvalues.length-1] != oldValue.innerText.trim())
{
CreateSuggest();
return ;
}
if(keycode == 32 && txtvalues[txtvalues.length-1] != oldValue.innerText.trim())
{
CreateSuggest();
return ;
}
//按向下创建提示
if(!CheckSuggest() && txtvalues[txtvalues.length-1].length !=0 && keycode == 40)
{//文本框有内容,提示不存在,向下
CreateSuggest();
return;
}
//当删除的时候,参量要初始化
if(keycode == 8 && txtvalues[txtvalues.length-1].length == 0)
{
DeleteSuggest();
oldValue.innerText = "";
return ;
}
if(CheckSuggest())
{
var inputIndex = document.getElementById("inputIndex");
var selectIndex = Number(inputIndex.value);
//排除上下控制的值操作外,其他任何值改变都要去创建提示框
if( selectIndex < 0 && txtvalues[txtvalues.length-1] != oldValue.innerText)
{
CreateSuggest();
return ;
}
if(keycode == 40)
{//向下
ChangeSelection(false);
return ;
}
if(keycode == 38)
{//向上
ChangeSelection(true);
return ;
}
if(keycode == 46 || keycode == 27)
{//del
DeleteSuggest();
oldValue.innerText = "";
return ;
}
var panelSuggest = document.getElementById("divSuggestPanel");
var tb = panelSuggest.getElementsByTagName("table")[0];
if(keycode == 13)
{//回车
if(selectIndex > -1 && txtvalues[txtvalues.length-1] != oldValue.innerText)
{
CreateSuggest();
return ;
}
RemoveSuggest();
return ;
}
}
if(txtvalues[txtvalues.length-1] != oldValue.innerText)
{//当上面的条件都筛选后,只要值发生改变就创建下拉提示
CreateSuggest();
return ;
}
}
}
//删除提示前对文本做相关操作
function RemoveSuggest()
{
if(CheckSuggest())
{
var panelSuggest = document.getElementById("divSuggestPanel");
var inputIndex = document.getElementById("inputIndex");
var txtvalues = objInputText.value.trim().split(";");
if( CheckActiveElement(panelSuggest) || event.keyCode == 13)
{
//做个判断,判断当前活动对象 是不是TD,是的话就执行下面的操作,不是的话就不做操作,或者把文本框作为当前活动
if(CheckActiveElement(panelSuggest) && document.activeElement.tagName != "TD")
{
objInputText.focus();
return ;
}
//得到选定的值
var selectIndex = Number(inputIndex.value);
if(selectIndex >= 0)
{
var tb = panelSuggest.getElementsByTagName("table")[0];
txtvalues[txtvalues.length-1] = tb.rows[selectIndex].cells[0].innerText;
objInputText.value = GetValues(txtvalues);
}
}
document.body.removeChild(inputIndex);
document.body.removeChild(panelSuggest);
oldValue.innerText = "";
}
else
{
return ;
}
}
//删除提示的方法,不对文本做任何操作
function DeleteSuggest()
{
if(CheckSuggest())
{
var panelSuggest = document.getElementById("divSuggestPanel");
var inputIndex = document.getElementById("inputIndex");
document.body.removeChild(inputIndex);
document.body.removeChild(panelSuggest);
}
}
//加载提示框
function CreateSuggest()
{
var txtvalues = objInputText.value.trim().split(";");
//提示框存在,而且文本框值与上次的输入不同时,才进行下面的加载工作
if(CheckSuggest())
{
if( oldValue.innerText.trim() == txtvalues[txtvalues.length-1].trim())
{
return ;
}
else
{
DeleteSuggest();
}
}
if(CheckSuggest() && txtvalues[txtvalues.length-1].trim().length ==0)
{//提示框存在,但是文本框没有内容,这时删除提示框
DeleteSuggest();
oldValue.innerText = "";
return ;
}
//如果输入为空格,就退出
if(txtvalues[txtvalues.length-1].trim().length == 0)
{
return ;
}
//从数据源中取数据
var suggestList = GetSuggestList();
if(suggestList == null||suggestList.length < 1)
{//对传入的数组进行判断,为空或者列表为0就退出
DeleteSuggest(); //开始的输入有提示,后面的输入可能没有提示,所以数据源为空时要尝试删除提示
oldValue.innerText = "";
return ;
}
oldValue.innerText = txtvalues[txtvalues.length-1]; //以上条件都符合,根据数据源来创建数据
var inputIndex = document.createElement("input"); //用隐藏控件来做索引的保存
inputIndex.type = "hidden";
inputIndex.id = "inputIndex";
inputIndex.value = -1;
var suggest = ""; //根据数据源来写div提示信息
suggest += "<table>";
for(var nIndex = 0; nIndex < suggestList.length; nIndex++)
{
suggest += "<tr onmouseover=\" for(var n=0;n<this.parentElement.rows.length;n++){this.parentElement.rows[n].className='trmouseout';};this.className='trmouseover';var inputIndex = document.getElementById('inputIndex');inputIndex.value = this.rowIndex; \" onmouseout=\"this.className='trmouseout';\" >";
suggest += suggestList[nIndex];
suggest += "</tr>";
}
suggest += "</table>";
var panelSuggest = document.createElement("div"); //创建装提示框的容器div
panelSuggest.id = "divSuggestPanel";
panelSuggest.className = "pnlSuggest"; //设置对象的类
panelSuggest.style.width = objInputText.clientWidth + "px"; //设置对象的宽度,与文本框宽度相同
panelSuggest.style.top = (GetPosition()[0] + objInputText.offsetHeight + 1) + "px";
panelSuggest.style.left = GetPosition()[1] + "px";
panelSuggest.innerHTML = suggest;
document.body.appendChild(panelSuggest); //把提示框和索引控件添加进来
document.body.appendChild(inputIndex);
//判断显示条数的多少,多于10条就用滚动条操作
if(suggestList.length > 10)
{
var h = panelSuggest.getElementsByTagName("tr")[1].offsetHeight;
panelSuggest.style.height = (h * 10) + "px";
panelSuggest.style.width = (objInputText.clientWidth + 20) + "px";
}
}
//更换选项
function ChangeSelection(isup)
{
if(CheckSuggest())
{
var txtvalues = objInputText.value.trim().split(";");
var inputIndex = document.getElementById("inputIndex"); //得到索引的值
var selectIndex = Number(inputIndex.value);
var panelSuggest = document.getElementById("divSuggestPanel"); //得到提示框
var tb = panelSuggest.getElementsByTagName("table")[0];
var maxIndex = tb.rows.length - 1; //提示信息的最大索引
if(isup)
{//向上
if(selectIndex >= 0) //索引不能为负
{
tb.rows[selectIndex].className = "trmouseout";
selectIndex--;
if(selectIndex >= 0)
{
tb.rows[selectIndex].className = "trmouseover";
}
}
}
else
{
if(selectIndex < maxIndex) //大于等于最大索引就不做任何操作
{
if(selectIndex >= 0)
{
tb.rows[selectIndex].className = "trmouseout";
}
selectIndex++;
tb.rows[selectIndex].className = "trmouseover";
}
}
inputIndex.value = selectIndex;
//控制滚动条的上下
if(selectIndex >= 0)
{
if(tb.rows[selectIndex].offsetTop < panelSuggest.scrollTop)
{
panelSuggest.scrollTop = tb.rows[selectIndex].offsetTop;
}
if(tb.rows[selectIndex].offsetTop + tb.rows[selectIndex].offsetHeight > panelSuggest.scrollTop + panelSuggest.offsetHeight)
{
panelSuggest.scrollTop = tb.rows[selectIndex].offsetTop + tb.rows[selectIndex].offsetHeight - panelSuggest.offsetHeight;
}
}
}
}
//判断活动对象是否为obj对象的从属对象
function CheckActiveElement(obj)
{
var isAe = false;
var objtemp = document.activeElement;
while(objtemp != null)
{
if(objtemp == obj)
{
isAe = true;
break;
}
objtemp = objtemp.parentElement;
}
return isAe;
}
//检查提示框是否存在
function CheckSuggest()
{
var panelSuggest = document.getElementById("divSuggestPanel");
if(panelSuggest == null)
{
return false;
}
else
{
return true;
}
}
//获取文本框的位置
function GetPosition()
{
var top = 0,left = 0;
var obj = objInputText;
do
{
top += obj.offsetTop; //距离顶部
left += obj.offsetLeft; //距离左边
}
while (obj = obj.offsetParent);
var arr = new Array();
arr[0] = top;
arr[1] = left;
return arr;
}
//得到提示数据
function GetSuggestList()
{
var txtvalues = objInputText.value.trim().split(";");
var txtfield = txtvalues[txtvalues.length-1];
var hiddenvaluefield = objInputText.parentElement.getElementsByTagName("h2")[0].innerText;
var showtextfield = objInputText.parentElement.getElementsByTagName("h3")[0].innerText;
var procedurename = objInputText.parentElement.getElementsByTagName("h4")[0].innerText;
var condition = objInputText.parentElement.getElementsByTagName("h5")[0].innerText;
var suggestlist = Suggest.GetSuggestData(txtfield,showtextfield,procedurename,condition).value;
return suggestlist;
}
//得到文本框的显示值
function GetValues(values)
{
var txtvalue="";
for(var n=0;n<values.length;n++)
{
if(values[n].trim().length==0)
{
continue;
}
txtvalue+=values[n]+";";
}
return txtvalue;
}
}
</script>
<div>
<input type="text" runat="server" id="txtInput" name="txtInput" onkeydown="ShowSuggest(this);" style="width: 320px" autocomplete="off" />
<h2 id="hneedfield" runat="server" class="hiddenValues"></h2>
<h3 id="hshowfield" runat="server" class="hiddenValues"></h3>
<h4 id="hprocedurename" runat="server" class="hiddenValues"></h4>
<h5 id="hhashvalue" runat="server" class="hiddenValues"></h5>
<h6 class="hiddenValues" style="display: none;"></h6>
</div>
2.2、对应的后端.CS文件:
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using System.Data.SqlClient;
using IIP.Data.SQLHelper;
/**//// <summary>
/// 转贴地址 http://www.cnblogs.com/huowujiyx/archive/2008/07/07/1237355.html
/// 使用说明:调用该控件,要设置控件的相关属性:
/// 显示提示的筛选字段名 TextField
/// 查询数据的存储过程名 Procedurename
/// 存储过程的各项参数 ProcedureParams(Hashtable类型,key值放参数名,value值为参数值)
/// 还可以通过下面的方法,设置域的值:
/// List<string> showFields 要下拉提示的相关信息 AddShowFields(string field)方法添加
/// 设置好上面的信息后就可以使用了,要得到文本框信息,通过属性Text得到
/// 要取得与文本信息相匹配的隐藏信息,也就是与hiddenFields对应的信息,通过属性Values得到
/// </summary>
public partial class Suggest : System.Web.UI.UserControl
{
private string textField;
/**//// <summary>
/// 设置要显示提示的筛选字段名
/// </summary>
public string TextField
{
set { textField = value; }
}
private string valueField;
/**//// <summary>
/// 设置要得到的唯一性标识的字段名
/// </summary>
public string ValueField
{
set { valueField = value; }
}
private string procedurename;
/**//// <summary>
/// 设置查询数据的存储过程名
/// </summary>
public string Procedurename
{
set { procedurename = value; }
}
private Hashtable procedureParams = new Hashtable();
/**//// <summary>
/// 设置存储过程的各项参数
/// </summary>
public Hashtable ProcedureParams
{
set { procedureParams = value; }
}
private List<string> showFields = new List<string>();
/**//// <summary>
/// 添加显示内容的字段
/// </summary>
/// <param name="field">字段名</param>
public void AddShowFields(string field)
{
showFields.Add(field);
}
/**//// <summary>
/// 得到文本框的值
/// </summary>
public string Text
{
get
{
string[] texts = txtInput.Value.Split(new string[] { ";" }, StringSplitOptions.None);
DataTable dt = GetDataSourse(this.hprocedurename.InnerText, this.hhashvalue.InnerText);
string txtvalue = "";
foreach (string text in texts)
{
DataRow[] drs = dt.Select(this.hshowfield.InnerText.Split(new string[] { "$h,w$" }, StringSplitOptions.RemoveEmptyEntries)[0] + " = '" + text + "'");
if (drs.Length > 0)
{
txtvalue += text + ",";
}
}
return txtvalue;
}
}
/**//// <summary>
/// 得到与文本框值匹配的信息
/// </summary>
public string Values
{
get
{
string[] texts = txtInput.Value.Split(new string[] { ";" }, StringSplitOptions.None);
DataTable dt = GetDataSourse(this.hprocedurename.InnerText, this.hhashvalue.InnerText);
string value = "";
foreach (string text in texts)
{
DataRow[] drs = dt.Select(this.hshowfield.InnerText.Split(new string[] { "$h,w$" }, StringSplitOptions.RemoveEmptyEntries)[0] + " = '" + text + "'");
if (drs.Length > 0)
{
value += drs[0][this.hneedfield.InnerText.Trim()].ToString() + ",";
}
}
return value;
}
}
/**//// <summary>
/// 得到数据源
/// </summary>
/// <param name="procedurename">存储过程名</param>
/// <param name="condition">存储过程参数</param>
/// <returns>查询数据源</returns>
private DataTable GetDataSourse(string procedurename, string condition)
{
//从数据源得到数据
string[] keyandvalue = condition.Split(new string[] { "$h,w$" }, StringSplitOptions.RemoveEmptyEntries);
SqlParameter[] commandparameters;
if (keyandvalue.Length == 0)
{
commandparameters = null;
}
else
{
commandparameters = new SqlParameter[keyandvalue.Length];
for (int i = 0; i < commandparameters.Length; i++)
{
string[] tempvalue = keyandvalue[i].Split(new string[] { "$h,57,w$" }, StringSplitOptions.None);
commandparameters[i] = new SqlParameter(tempvalue[0], SqlDbType.VarChar, 500);
commandparameters[i].Value = tempvalue[1];
}
}
DataSet ds = SqlHelper.ExecuteDataset(Comm.connectionString, CommandType.StoredProcedure, procedurename, commandparameters);
if (ds == null || ds.Tables[0] == null)
{
return null;
}
else
{
return ds.Tables[0];
}
}
/**//// <summary>
/// 根据输入内容,得到数据
/// </summary>
/// <param name="txtfield">显示文本</param>
/// <param name="showtextfield">下拉提示的相关字段</param>
/// <param name="procedurename">存储过程名</param>
/// <param name="condition">存储过程参数</param>
[Ajax.AjaxMethod]
public string[] GetSuggestData(string txtfield, string showtextfield, string procedurename, string condition)
{
//数据的要显示字段和要相关得到数值的字段
string[] shows = showtextfield.Split(new string[] { "$h,w$" }, StringSplitOptions.RemoveEmptyEntries);
//数据的筛选
DataTable dt = GetDataSourse(procedurename, condition);
if (dt == null)
{
return null;
}
DataRow[] drs = dt.Select(shows[0] + " like '%" + txtfield + "%'");
if (drs.Length == 0)
{
return null;
}
//将数据做为显示流
List<string> fields = new List<string>();
for (int j = 0; j < shows.Length; j++)
{
fields.Add(shows[j]);
}
string[] tbs = new string[drs.Length];
int count = 0;
foreach (DataRow dr in drs)
{
foreach (string item in fields)
{
tbs[count] += "<td>" + dr[item].ToString() + "</td>";
}
count++;
}
return tbs;
}
/**//// <summary>
/// 页面的相关信息的保存
/// </summary>
private void BindValue()
{
//页面中加上与文本输入相关的有下拉提示的字段
this.hshowfield.InnerText = this.textField;
int i = 0;
foreach (string item in showFields)
{
this.hshowfield.InnerText += "$h,w$";
this.hshowfield.InnerText += item;
i++;
}
//页面中加上查询数据源的存储过程
this.hprocedurename.InnerText = this.procedurename;
//页面中加上与存储过程匹配的参数
this.hhashvalue.InnerText = "";
if (procedureParams != null && procedureParams.Count > 0)
{
i = 0;
foreach (DictionaryEntry temp in procedureParams)
{
if (i != 0)
{
this.hhashvalue.InnerText += "$h,w$";
}
this.hhashvalue.InnerText += temp.Key.ToString() + "$h,57,w$" + temp.Value.ToString();
i++;
}
}
//唯一标识的字段名
this.hneedfield.InnerText = this.valueField;
}
protected void Page_Load(object sender, EventArgs e)
{
Ajax.Utility.RegisterTypeForAjax(typeof(SearchText));
if (!IsPostBack)
{
BindValue();
}
}
}
然后在aspx页面就可以拖这个控件来用,不过在其page_load方法中,要对控件的两个控件的数据源进行设置,不然会因没有数据而不弹出提示框。
三、使用 JQuery Framework 来实现:
经笔者亲自测试作者给出的例子,感觉比较强大,且支持 Firefox 2, IE 6 & 7, Opera 9, Safari 3,已完全满足需求,所以 MoFun.CC 推荐您重点考虑这个:
文档介绍:http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/
演示地址:http://jquery.bassistance.de/autocomplete/demo/
四、使用 Suggest Framework 来实现:
不过这种方式,给出的例子似乎没有下拉项的过滤功能那个能,具体参见:
http://alex.dojotoolkit.org/shrinksafe/
http://alex.dojotoolkit.org/shrinksafe/