一时兴起用Ajax配合JavaScript写实现自动补全功能的TextBox

 坐了一夜的车终于到家了!这一夜坐车的感觉真的太。。。。。。

还好我的本本比较争气,昨天晚上开节能模式,电池竟然撑了将近三个小时,刚好完成一个能自动补全的TextBox。
下面就描述下过程,希望和有兴趣朋友交流一下优缺点:
开机以后想到离开公司的时候记得MVC的项目中需要一个类似于能够实现自动补全的TextBox,随即打开思路。
需要实现的功能 
1:该控件可以从数据库中读取数据
2:该控件应该在用户更改TextBox中内容时使用Ajax向服务请求相应数据
3:使用Javascript控制从服务请求到的数据的展现方式
4:使用Javascript注册相关事件
嗯  基本功能就是这样了! 那就开始吧!
  启动SQL2008与VS2010后查看了一下可用资源,嗯 还不错数据库中有一个包括全国500多个城市的表(表名:Macaco.CityCollection),这张表有三个字段第一个是AutoID 不用说肯定是自动编号列,第二个是EndNumber 嗯这个比较历害!我也不知道是干什么的 跳过!第三个字段是City里面是城市名。
  VS2010打开后加载了一下我的Demo工程 首先在Tools类库项目下添加了一个数据库表对象属性模型类,其次添加了一个数据库表数据访问类。
代码如下: 
数据表对象属性模型类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Macaco.Demo.Tools
{
    
public class CityModel
    {
        
public int ID { getset; }

        
public int EndNumber { getset; }

        
public string City { getset; }
    }
}
数据表数据访问类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace Macaco.Demo.Tools
{
    
public class CityCollectionTools
    {

        
//以下为从数据库中获取城市信息的数据访问方法
        string strcon = "server=.;uid=DemoUser;pwd=DemoUser;DataBase=MacacoOnline;";

        
public List<CityModel> GetCityDataByEndNumberAndCity(string sqlPar)
        {
            List
<CityModel> cityList = null;
            
if (string.IsNullOrEmpty(sqlPar))
                
return cityList;

            
//此处我们以模糊查询的方法查询前10条符合条件的记录  此处以ID排序实际应用中应该要有一个优先级的字段并以此字段排序
            string strcmd = "select top 10 ID,EndNumber,City from Macaco.CityCollection where EndNumber like '%'+@SqlPar+'%' or City like '%'+@SqlPar+'%' order by ID";

            SqlCommand sqlcmd 
= new SqlCommand(strcmd, new SqlConnection(strcon));

            sqlcmd.Parameters.Add(
new SqlParameter("@SqlPar", SqlDbType.VarChar, 20)).Value = sqlPar;

            SqlDataReader sdr 
= null;
            
try
            {
                sqlcmd.Connection.Open();
                sdr 
= sqlcmd.ExecuteReader();
                cityList 
= new List<CityModel>();
                CityModel cityData 
= null;
                
while (sdr.Read())
                {
                    cityData 
= new CityModel()
                    {
                        ID 
= (sdr[0== null ? cityData.ID : sdr.GetInt32(0)),
                        EndNumber 
= (sdr[1== null ? cityData.EndNumber : sdr.GetInt32(1)),
                        City 
= (sdr[2== null || string.IsNullOrEmpty(sdr.GetString(2)) ? cityData.City : sdr.GetString(2))
                    };
                    cityList.Add(cityData);
                }
            }
            
finally
            {
                
if (sdr != null && !sdr.IsClosed)
                    sdr.Close();

                
if (sqlcmd.Connection.State == ConnectionState.Open)
                    sqlcmd.Connection.Close();
            }
            
return cityList;
        }
    }
}

 

   现在需要一个可以响应客户端Ajax请求的程序处理文件,所以我们要在MVC项目添加一个一般事件处理程序命名为GetCityData.ashx(后缀名为.ashx的文件)咦 怎么 这文件有点不对啊 多了一个GetCityData.ashx.cs文件 以前aspx中没有这个文件 算了不管它了,如果不行再说吧(事实证明可以和以前一样使用)! 我把它放到了站点根目录下的一个叫AjaxHelp(自己建的)的文件夹中 然后在里面

添加如下代码:

用于响应Ajax请求的一般事件处理程序
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Macaco.Demo.Tools;
using System.Text;

namespace Macaco.Demo.MvcWeb.AjaxHelp
{
    
/// <summary>
    
/// 获取城市信息的Http管道
    
/// </summary>
    public class GetCityData : IHttpHandler
    {
        
public void ProcessRequest(HttpContext context)
        {
            
//清除缓冲区中的所有内容输出
            context.Response.Clear();
            
//创建数据访问对象
            CityCollectionTools ccTools = new CityCollectionTools();
            
//声明变量用于存放从URL中取得的参数
            string strPar = null;
            
try
            {
                
//获取URL中的参数
                strPar = context.Request.QueryString[0];
            }
            
catch
            {
                
//若出现异常向客户端响应一个空的输出并终止该方法
                context.Response.Write("");
                
return;
            }
            
//获取数据库中的数据
            List<CityModel> cityData = ccTools.GetCityDataByEndNumberAndCity(strPar);

            
//创建一个可变长度的字符串
            StringBuilder sbCityData = new StringBuilder();

            
//遍历获取到的数据拼接成一个字符串
            foreach (CityModel city in cityData)
            {
                sbCityData.Append(
"<p>(" + city.EndNumber + "" + city.City + "</p>");
            }

            
//响应客户端输出
            context.Response.Write(sbCityData);
        }

        
/// <summary>
        
/// 该属性指示其他请求是否可以与当前请求共享当前的IHttpHandler实例  此属性与我们现的的操作没有关系 默认即可
        
/// </summary>
        public bool IsReusable
        {
            
get
            {
                
return false;
            }
        }
    }
}

    这段代码主要功能是通过之前添加的数据库表数据访问类中的GetCityDataByEndNumberAndCity方法从数据库模糊查询相应数据并对数据进行简单的格式处理。最后以Response.Write方式响应客户端的请求至此服务端的功能代码基本上就完成了!

   现在我们开始添加客户端的功能代码!在此向大家呼吁一下生活中应该注意环保,懂得资源利用才好(我就不在新建文件了 使用的是MVC项目Home下的Index)!首先在MVC项目下找到Index页面(具体路径应该是:Views/Home/Index.aspx),打开后

添加如下代码:

客户端功能-前台页面Index
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    主页
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    
<%--这是MVC中添加TextBox控件的方法等同与 <input id="txtCity" name="txtCity" value=""/>--%>
    
<%: Html.TextBox("txtCity"%><br />
    
<%--这就是数据的舞台 从服务获取到的数据都会在这个DIV中展现--%>
    
<div id="ShowDataDiv" class="showDataDivStyle" title="txtCity">
    
</div>
    
    
<%--以下这段JS是要向文档的Head标中添加一个Js文件的链接--%>
    
<script type="text/javascript" language="javascript">
        
//获取当前文档中Head标记
        var head = document.getElementsByTagName("head")[0];
        
//创建一个script标记
        var script = document.createElement("script");
        
//设置相关属性
        script.src = "Scripts/MacacoJSTools.js";
        script.type 
= "text/javascript";
        
//追加到Head开始标记之后结束标记之前的位置
        head.appendChild(script);
    
</script>
    
<div style="background: silver; margin: 0px; padding: 0px;">
        
<b>
            
<%: ViewData["Message"%></b>
        
<p>
            若要了解有关 ASP.NET MVC 的更多信息,请访问 
<href="http://asp.net/mvc" title="ASP.NET MVC 网站">http://asp.net/mvc</a>
        
</p>
    
</div>
</asp:Content>

    前台页面就是这样,需要注意的是我在页面中使用JS脚本引入了下面即将要写的JS文件。现在我们要添加一个Js文件我把它放在根目录下的Scripts文件夹下并命名为MacacoJsTools.js并在里面添加了包括创建 发送 响应 Ajax请求以及TextBox控件 P标记 Div标记 的事件绑定的代码 呵呵 这次比较多一点 不过基本上每一句都有注释或提示

详细代码如下 :

MacacoJsTools.js详细代码如下
//封装documnet.getElementById方法
function $(tarGetID) { return document.getElementById(tarGetID); }

//获取操作区域
var showDataDiv = $("ShowDataDiv");

//获取TextBox控件的引用(注:title是TextBox控件的ID)
var controlInpputID = $(showDataDiv.title);

//设置初始状态为不可见(隐藏)
showDataDiv.style.display = "none";

//为TextBox控件注册事件
controlInpputID.onkeyup = function () { SendRequest(this.value) };
controlInpputID.onclick 
= function () { SendRequest(this.value) };
controlInpputID.onblur 
= function () {//注:此处注册的事件是当TextBox失去焦点时为在150毫秒调用一个匿名方法把显示数据的DIV隐藏
    setTimeout(function () {
        showDataDiv.style.display 
= "none";
    }, 
150)
};

//创建XMLHttpRequest对象实例
function GetXMLHttpRequest() {
    
//声明对象用于指XMLHttpRequest(创建成功指向XMLHttpRequest,失败指向一个bool类型值)
    var Http_Request = false;

    
//根据浏览器创建不同的对象实例
    if (window.XMLHttpRequest) {

        
//创建基于Mozilla浏览器的XMLHttpRequest对象实例
        Http_Request = new XMLHttpRequest();

        
//判断请求类型是否有默认设置
        if (Http_Request.overrideMimeType) {

            
//若无默认设置则设置为text/xml格式
            Http_Request.overrideMimeType = "text/xml";
        }
    } 
else if (window.ActiveXObject) {
        
//创建基于IE浏览器XMLHttpRequest的对象实例
        try {

            
//创建针对较新版本IE浏览器的XMLHttpRequest对象实例
            Http_Request = new ActiveXObject("Msxml2.XMLHTTP");

        } 
catch (e) {

            
try {

                
//创建针对较旧版本IE浏览器的XMLHttpRequest对象实例
                Http_Request = new ActiveXObject("Microsoft.XMLHTTP");

            }
            
catch (e) {

                alert(
"您的浏览器不支持Ajax的处理操作,无法正常协助您完成查询操作!");

            }
        }
    }

    
//返回导步请求处理对象
    return Http_Request;
}

//创建全局导步应用处理对象
var httpRequst;

function SendRequest(strPar) {
    
if (strPar.length > 0) {

        
//获取并赋值全局异步应用处理对象
        httpRequst = GetXMLHttpRequest();

        
//判断是否创建成功
        if (httpRequst) {

            
//指定状态更改后的操作
            httpRequst.onreadystatechange = ResponseHttpRequest;


            
var url = "/AjaxHelp/GetCityData.ashx?" + encodeURI(strPar);


            
//打开发送请求,指定方式及要请求的文件以及是否为异步调用
            httpRequst.open("GET", url, true);

            
//发送请求
            httpRequst.send(null);
        }
    } 
else
        showDataDiv.style.display 
= 'none';
}


//创建用于响应请求结果的方法
function ResponseHttpRequest() {

    
if (httpRequst.readyState == 4) {

        
//判断是否正确响应
        if (httpRequst.status == 200) {

            
//判断要操作的DIV是否获取到
            if (showDataDiv != null) {
                
var reqText = httpRequst.responseText;
                
//判断是否查询到数据如果查询到的数据较少则为不正常应将显示数据的DIV隐藏
                if (reqText.length > 10) {

                    
/*将获取的文本添加到要显示数据的DIV中(注:此处不使用innerText是因为Firefox不完全支持该属性
                    (详细可以参考http://www.google.com.hk/)并且请求的数据中包括了HTML标记而我需要这些HTML标记
                    被浏览器解析并应用样式)
*/
                    showDataDiv.innerHTML 
= reqText;

                    
//获取DIV中所有的p村记即上面添加进去的HTML标记
                    var pCol = showDataDiv.getElementsByTagName("p");

                    
//使用循环遍历所有P标记为其添加对应的事件与属性
                    for (i = 0; i < pCol.length; i++) {
                        
//当用户点击某个P标记时则将该P标记内的内容放入TextBox控件中并将DIV隐藏
                        pCol[i].onclick = function () { controlInpputID.value = this.innerHTML; showDataDiv.style.display = "none"; }
                        
//设置用户将鼠标悬停在当前P标记上时显示提示信息即P标记中的所有内容
                        pCol[i].title = pCol[i].innerHTML;
                    }
                    
//显示从服务获取到的数据
                    showDataDiv.style.display = "block";
                } else {
                    
//如果没有获取到数据隐藏DIV
                    showDataDiv.style.display = "none";
                }
            }
        }
    }
}

   到此JS的脚本我们就写完了 现在我们还需要为我们的自动补全控件添加一点样式(当然还有一个至关重要属性控制),添加一个CSS文件放到根目录下Content文件夹中我将其命名为MacacoStyleSheet.css 

 添加如下代码: 

控制TextBox与Div样式的样式表MacacoStyleSheet.css
.showDataDivStyle
{
    width
: 202px;
    height
: 210px;
    line-height
: 15px; /*该属性用于将DIV从内存流中拖出打破原有的样式*/
    position
: absolute;
    background-color
: #ffeeee;
    font-size
: 12px;
    border
: 1px solid silver; /*让Div与TextBox更亲近一点*/
    margin-top
: -1px;
}
.showDataDivStyle p
{
    cursor
: pointer;
    width
: 190px;
    border
: 1px solid #ffeeee;
    border-left
: none;
    border-right
: none;
    margin
: 0px auto;
}
/*以下是设置鼠标悬停在P标记上时的样式*/
.showDataDivStyle p:hover
{
    border
: 1px solid blue;
    border-left
: none;
    border-right
: none;
    background-color
: Purple;
}

   至此所有的代码都已经完成了 现在只需要将该CSS文件的引用添加到页面中就可以测试了 我已经在 IE8,IE9,Safari,遨游3,Opera,Chrome,Firefox 4.0 Beta8测试过 除了Firefox 反应稍微慢1秒左右 其它都可以算的上是完美兼容!唯一遗憾的是 我用的是Win7 IECollection装不了所以IE8以下的浏览器没有测试过,估计在IE6下会有点样式上的偏移,不过判断一下浏览器的版本就可以解决! 如果有朋友在IE6下测试该段代码 请将结果粘到回复中 同时我在上面也有提出一些疑问(例如:为什么MVC项目中的一般事件处理程序为什么要比aspx中的多一个以.cs后缀的文件) 如果有朋友知道在回复中解释一下 谢谢!

可以能过链接下载  项目源码 我已经将Tools项目中的代码文放到了App_Code中项目数据库文件放到了App_Data中您可能需要附加到MSSQL中才可以运行!

 

posted @ 2011-01-17 09:44  macaco  阅读(1669)  评论(1编辑  收藏  举报