Asp.Net MVC 扩展联想控件

在web中,为改善用户体验,我们常会将一些文本输入框做成智能联想,以让用户更快更准确的输入内容。大概是这样的:当用户开始在文本框输入时,客户端脚本ajax向服务端发起请求,服务端从数据库读取返回数据,客户端解析数据附加在文本框的下拉div中供用户选择参考。

在MVC中我们可以通过扩展HtmlHelper来封装自己写的控件,以便在整个项目中像使用 Html.TextBox("") 一样来使用自定义控件。

 

扩展代码如下

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace HtmlHelperExt
{
    public static class SuggestBoxExtensions
    {
        #region SuggestBox 联想控件

        /// <summary>
        /// 联想控件
        /// </summary>
        /// <param name="htmlHelper"></param>
        /// <param name="name">name(id)</param>
        /// <param name="value">value</param>
        /// <param name="controller">controller</param>
        /// <param name="action">action</param>
        /// <param name="action">fieldName 要在下拉框显示的DataTable中的字段名</param>
        /// <param name="action">callBack 当选择值后的回调脚本函数</param>
        /// <param name="htmlAttributes"></param>
        /// <returns></returns>
        public static string SuggestBox(this HtmlHelper htmlHelper, string name, object value, string controller, string action,string fieldName,string callBack,IDictionary<string, object> htmlAttributes)
        {

            return htmlHelper.SuggestBox(name, value, controller, action,"", fieldName, fieldName, "", "", "",callBack, htmlAttributes);
        }

        /// <summary>
        /// 联想控件
        /// </summary>
        /// <param name="htmlHelper"></param>
        /// <param name="name">name(id)</param>
        /// <param name="value">value</param>
        /// <param name="controller">controller</param>
        /// <param name="action">action</param>
        /// <param name="headerText">下拉选框的头部文字(要显示多列用 ';'隔开)</param>
        /// <param name="displayFields">要在下拉框显示的DataTable中的字段名(要显示多列用 ';'隔开)</param>
        /// <param name="valueField">要赋文本框的字段(只能是一个,且包含在displayFields中)</param>
        /// <param name="action">callBack 当选择值后的回调脚本函数</param>
        /// <param name="htmlAttributes"></param>
        /// <returns></returns>
        public static string SuggestBox(this HtmlHelper htmlHelper, string name, object value, string controller, string action, string headerText, string displayFields, string valueField, string callBack, IDictionary<string, object> htmlAttributes)
        {

            return htmlHelper.SuggestBox(name, value, controller, action, headerText, displayFields, valueField, "", "", "",callBack, htmlAttributes);
        }
        /// <summary>
        /// 联想控件
        /// </summary>
        /// <param name="htmlHelper"></param>
        /// <param name="name">name(id)</param>
        /// <param name="value">value</param>
        /// <param name="controller">controller</param>
        /// <param name="action">action</param>
        /// <param name="headerText">下拉选框的头部文字(要显示多列用 ';'隔开)</param>
        /// <param name="displayFields">要在下拉框显示的DataTable中的字段名(要显示多列用 ';'隔开)</param>
        /// <param name="valueField">要赋文本框的字段(只能是一个,且包含在displayFields中)</param>
        /// <param name="keyField">选择行的主键</param>
        /// <param name="keyTextBoxName">将主键值保存在以此命名的隐藏的文本控件中,可供其他地方使用</param>
        /// <param name="keyTextBoxValue">初始化时主键文本控件中的值</param>
        /// <param name="htmlAttributes"></param>
        /// <returns></returns>
        public static string SuggestBox(this HtmlHelper htmlHelper, string name, object value, string controller, string action, string headerText, string displayFields, string valueField, string keyField, string keyTextBoxName, string keyTextBoxValue,string callBack,IDictionary<string, object> htmlAttributes)
        {
            var sb = new StringBuilder();

            if (htmlAttributes == null)
                htmlAttributes = new Dictionary<string, object>();

            string styleStr = "";
            if (htmlAttributes.ContainsKey("style"))
                styleStr = htmlAttributes["style"].ToString();
            string boxId = name.ToUpper() + "_SUGBOX";
            if (styleStr.Length > 0)
                sb.Append(htmlHelper.TextBox(name, value, new { style = styleStr, autocomplete = "off" }));
            else
                sb.Append(htmlHelper.TextBox(name, value, new { autocomplete = "off" }));

            sb.Append("<script type=\"text/javascript\">");

            sb.AppendFormat("$('{0}').suggest({{boxId:'{1}',controller:'{2}',action:'{3}',headerText:'{4}',displayFields:'{5}',valueField:'{6}',keyField:'{7}',keyTextBoxName:'{8}',callBack:'{9}'}})",

                                        "#" + name, boxId, controller, action, headerText, displayFields, valueField, keyField, keyTextBoxName,callBack);

            sb.Append("</script>");
            if (keyTextBoxName != "")
            {
                sb.Append(htmlHelper.Hidden(keyTextBoxName, keyTextBoxValue));
            }
            return sb.ToString();
        }
        #endregion
    }
}

 

通过Controller读取、解析、返回数据。将从数据库(或XML)读取的数据存入DataTable,然后转换为Json字符串再返回给客户端。本Demo中模拟数据在XML文件中。

Controller代码如下:

View Code
 public class SuggestBoxController : Controller
    {
        public ActionResult Demo()
        {
            return View();
        }
        public string Suggest()
        {
            string searchText = "";
            if (Request["param"] == null)
            {
                return "";
            }
            searchText = Request["param"].ToString();
            DataSet ds = new DataSet();
            ds.ReadXml(Server.MapPath("~/KeyWords.xml"));
            DataRow[] drs = ds.Tables[0].Select("name like '%" + searchText + "%'");
            DataTable dt = new DataTable();
            dt.Columns.AddRange(new DataColumn[] { new DataColumn("id"), new DataColumn("name") });
            int len = drs.Length;
           
            for (int i = 0; i < len; i++)
            {
                DataRow dr = dt.NewRow();
                dr[0] = drs[i][0];
                dr[1] = drs[i][1];
                dt.Rows.Add(dr);                
            }
           
            
            return CreateJsonStr(dt);
        }

        #region CreateJsonStr
        /// <summary>
        /// 将DataTable数据转换为Json字符串
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public  string CreateJsonStr(DataTable dt)
        {

            StringBuilder JsonString = new StringBuilder();
            JsonString.Append("{ ");
            JsonString.Append("\"Data\":[ ");
            if (dt != null && dt.Rows.Count > 0)
            {

                for (int i = 0; i < dt.Rows.Count; i++)
                {
                    JsonString.Append("{ ");
                    for (int j = 0; j < dt.Columns.Count; j++)
                    {
                        if (j < dt.Columns.Count - 1)
                        {
                            JsonString.Append("\"" + dt.Columns[j].ColumnName.ToString() + "\":" + "\"" + dt.Rows[i][j].ToString() + "\",");
                        }
                        else if (j == dt.Columns.Count - 1)
                        {
                            JsonString.Append("\"" + dt.Columns[j].ColumnName.ToString() + "\":" + "\"" + dt.Rows[i][j].ToString() + "\"");
                        }
                    }

                    if (i == dt.Rows.Count - 1)
                    {
                        JsonString.Append("} ");
                    }
                    else
                    {
                        JsonString.Append("}, ");
                    }
                }

            }
            JsonString.Append("]}");
            return JsonString.ToString();
        }
        #endregion
    }

 

主要核心还是在客户端的脚本中,脚本通过ajax访问服务端,并加载绑定返回数据,响应反馈用户的操作。

View Code
(function($) {
    var itemIndex = 0;
    $.fn.suggest = function(options) {
        var params = {
            boxId: "suggestBox",
            boxWidth: 250,
            boxHeight: 200,
            controller: "",
            action: "",
            headerText: "",
            displayFields: "",
            valueField: "",
            keyField: "",
            keyTextBoxName: "",
            callBack: ""
        };
        var ops = $.extend(params, options);
        var headerTextArr = new Array();
        var displayFieldsArr = new Array();
        headerTextArr = ops.headerText.split(';');
        displayFieldsArr = ops.displayFields.split(';');
        var headerStr = "";
        var headerLen = headerTextArr.length;

        if (headerLen == 1 || headerLen == 0) {
            var textBox = $(this);
             
            ops.boxWidth = textBox.css("width");

        }

        var box = '';
        if (ops.headerText.length == 0) {

            box = '<div id="' + ops.boxId + '" style="display:none;width:' + ops.boxWidth + ';height:' + ops.boxHeight + '"><ul class="suggestBoxItems"></ul></div>';

        }
        else {
            for (var i = 0; i < headerLen; i++) {
                if (i == headerLen - 1) {
                    headerStr += '<span class="headerTextShort">' + headerTextArr[i] + '</span>'
                }
                else {
                    headerStr += '<span class="headerTextLong">' + headerTextArr[i] + '</span>'
                }
            }
            box = '<div id="' + ops.boxId + '" style="display:none;width:' + ops.boxWidth + ';height:' + ops.boxHeight + '"><div class = "headerText">' + headerStr + '</div><ul class="suggestBoxItems"></ul></div>';

        }
        $(this).after(box);

        var itemCount = 0;
        $(this).bind('keyup', function(e) {
            var value = $.trim($(this).val());
            if (value.length >= 1) {
                var position = $(this).position();

                $('#' + ops.boxId).css({ 'display': 'block', 'background': 'white', 'color': 'black', 'position': 'absolute', 'border': "1px solid #D5D5D5", 'left': position.left, 'top': position.top + 22 });
                var pVal = $(this).val() + "";
                if (pVal.search('&') >= 0) {
                    pVal = pVal.replace('&', '%26');
                }
                if (e.keyCode != 38 && e.keyCode != 40 && e.keyCode != 13 && e.keyCode != 9) {
                    var sugTextBox = $(this);
                    var dataUrl = "/" + ops.controller + "/" + ops.action;
                    if (pVal != "") {
                        $.ajax({
                            type: "post",
                            async: true,
                            url: dataUrl,
                            data: "param=" + pVal,
                            dataType: "json",
                            cache: false,
                            timeout: 5000,
                            beforeSend: loading(ops.boxId),
                            error: function(XMLHttpRequest, textStatus, errorThrown) {
                                alert(textStatus);
                                $('#' + ops.boxId).slideUp("slow");
                                $('#' + ops.boxId + ' ul').html('');
                            },
                            success: function(data) {
                                initBox(ops.boxId, sugTextBox, data, displayFieldsArr, ops.valueField, ops.keyField, ops.keyTextBoxName);
                            }

                        });
                    }

                    itemIndex = 0;
                }
                var itemCount = $('#' + ops.boxId + ' ul li').length;
                switch (e.keyCode) {
                    case 38:
                        if (itemIndex == 0) {
                            itemIndex = itemCount + 1;
                        }
                        if (itemIndex > 1) {
                            $('#' + ops.boxId + ' ul li:nth-child(' + itemIndex + ')').css({ 'background': 'white', 'color': 'black' });
                            itemIndex--;
                        }

                        $('#' + ops.boxId + ' ul li:nth-child(' + itemIndex + ')').css({ 'background': '#7AADEB', 'color': 'white' });
                        $(this).val($('#' + ops.boxId + ' ul li:nth-child(' + itemIndex + ')').find('font').text());
                        if (ops.keyTextBoxName != "") {
                            $('#' + ops.keyTextBoxName).val($('#' + ops.boxId + ' ul li:nth-child(' + itemIndex + ')').find('div').text());
                        }
                        break;
                    case 40:
                        if (itemIndex < itemCount) {
                            $('#' + ops.boxId + ' ul li:nth-child(' + itemIndex + ')').css({ 'background': 'white', 'color': 'black' });
                            itemIndex++;
                        }

                        $('#' + ops.boxId + ' ul li:nth-child(' + itemIndex + ')').css({ 'background': '#7AADEB', 'color': 'white' });
                        $(this).val($('#' + ops.boxId + ' ul li:nth-child(' + itemIndex + ')').find('font').text());
                        if (ops.keyTextBoxName != "") {
                            $('#' + ops.keyTextBoxName).val($('#' + ops.boxId + ' ul li:nth-child(' + itemIndex + ')').find('div').text());
                        }
                        break;
                    case 13:
                        if (itemIndex > 0 && itemIndex <= itemCount) {
                            $(this).val($('#' + ops.boxId + ' ul li:nth-child(' + itemIndex + ')').find('font').text());
                            if (ops.keyTextBoxName != "") {
                                $('#' + ops.keyTextBoxName).val($('#' + ops.boxId + ' ul li:nth-child(' + itemIndex + ')').find('div').text());
                            }
                            $('#' + ops.boxId).slideUp("fast");
                            $('#' + ops.boxId + ' ul').html('');
                            eval(ops.callBack);
                        }
                        break;
                    default:
                        break;
                }
            }
            else {

                $('#' + ops.boxId).slideUp("fast");
                $('#' + ops.boxId + ' ul').html('');
            }
        });
        $(this).blur(function() {
            var tempLi = $('#' + ops.boxId + ' ul li:nth-child(1)');

            if (itemIndex == 0 && tempLi != undefined) {

                $(this).val(tempLi.find('font').text());
                if (ops.keyTextBoxName != "") {
                    $('#' + ops.keyTextBoxName).val(tempLi.find('div').text());
                }
                itemIndex = 1;
            }
            if ($('#' + ops.boxId + ' ul').html() != '') {
                eval(ops.callBack);
            }


            $('#' + ops.boxId).slideUp("fast");
            $('#' + ops.boxId + ' ul').html('');
        });


    };

    function loading(boxId) {
        $('#' + boxId + ' ul').html('<img alt="loading" src="/Scripts/SuggestBox/loading.gif"/>');

    }
    function initBox(boxId, obj, data, displayFieldsArr, valueField, keyField, keyTextBoxName) {

        var str = "";
        if (data == undefined || data.Data == undefined || data.Data.length == 0) {
            $('#' + boxId + ' ul').html('<div class="noRecordsTip">No records found<div>');
        }
        else {

            for (var i = 0; i < data.Data.length; i++) {
                var fieldStr = "";
                for (var j = 0; j < displayFieldsArr.length; j++) {
                    if (displayFieldsArr[j] == valueField) {
                        if (j == 0 || j != displayFieldsArr.length - 1) {
                            fieldStr += "<font class='singleField'>" + data.Data[i][displayFieldsArr[j]] + "</font>";
                        }
                        else {
                            fieldStr += "<font>" + data.Data[i][displayFieldsArr[j]] + "</font>";

                        }
                    }
                    else {
                        var tempValue = data.Data[i][displayFieldsArr[j]];

                        if (tempValue.length > 16) {
                            tempValue = tempValue.substr(0, 16) + "...";
                        }
                        fieldStr += "<span class='commonFields'>" + tempValue + "</span>";
                    }
                }
                if (keyField != "") {
                    fieldStr += "<div style = 'display:none;'>" + data.Data[i][keyField] + "</div>";
                }

                str += "<li>" + fieldStr + "</li>";
            }
            $('#' + boxId + ' ul').html(str);
        }

        if (data != undefined && data.Data != undefined && data.Data.length == 1) {
            var tempLi = $('#' + boxId + ' ul li');
            obj.val(tempLi.find('font').text());
            if (keyTextBoxName != "") {
                $('#' + keyTextBoxName).val(tempLi.find('div').text());
            }
            itemIndex = 1;
        }
        $('#' + boxId + ' ul li').each(function() {
            $(this).bind('click', function() {
                obj.val($(this).find('font').text());
                if (keyTextBoxName != "") {
                    $('#' + keyTextBoxName).val($(this).find('div').text());
                }
                eval(ops.callBack);
                $('#' + boxId).slideUp("fast");

            });
        });

        $('#' + boxId + ' ul li').each(function() {
            $(this).hover(
                function() {
                    $('#' + boxId + ' ul li:nth-child(' + itemIndex + ')').css({ 'background': 'white', 'color': 'black' });
                    itemIndex = $('#' + boxId + ' ul li').index($(this)[0]) + 1;
                    $(this).css({ 'background': '#7AADEB', 'color': 'white' });
                    obj.val($(this).find('font').text());
                    if (keyTextBoxName != "") {
                        $('#' + keyTextBoxName).val($(this).find('div').text());
                    }

                },
                function() {
                    $(this).css({ 'background': 'white', 'color': 'black' });
                }
            );
        });
    };
})(jQuery);

在View里面需要Import我们写的扩展类所在的命名空间,<%@ Import Namespace="HtmlHelperExt" %>

以及引入相关的js、css(extension.suggestbox.js 和 jquery-1.4.1.js 和 SugBoxStyle.css)

View Code
<div>
       <% IDictionary<string, object> htmlAttributes = new Dictionary<string, object>(); htmlAttributes.Add("style", "width:198px"); %>

       简单单列 <%=Html.SuggestBox("MySuggestBox01", "", "SuggestBox", "Suggest","name","", htmlAttributes)%>
<br /><br /><br /><br />
      带表头单列  <%=Html.SuggestBox("MySuggestBox02", "", "SuggestBox", "Suggest","关键字","name","name","", htmlAttributes)%>

<br /><br /><br /><br />
       带表头双列 <%=Html.SuggestBox("MySuggestBox03", "", "SuggestBox", "Suggest","编号;关键字","id;name","name","", htmlAttributes)%>

<br /><br /><br /><br />
      带表头双列+回调函数  <%=Html.SuggestBox("MySuggestBox04", "", "SuggestBox", "Suggest", "编号;关键字", "id;name", "name", "id", "MySuggestBox04_ID", "0", "afterSelect()", htmlAttributes)%>
<span id="tip"></span>

<script type="text/javascript">
    var id = document.getElementById("MySuggestBox04_ID").value;
    document.getElementById("tip").innerHTML = "当前选择的编号是: <font color='red'>" + id + "</font>";
    function afterSelect() {

        var id = document.getElementById("MySuggestBox04_ID").value;
         document.getElementById("tip").innerHTML = "当前选择的编号是: <font color='red'>"+id+"</font>";
        
    }
 </script>
    </div>

结果演示一:简单单列

结果演示二:带表头单列

结果演示三:带表头双列

结果演示四:带表头双列+回调函数(选择一值时将key值赋给指定的Hidden中)

初写博客,还请大牛们多多指教

posted @ 2013-02-06 10:54  bright-lin  阅读(2772)  评论(11编辑  收藏  举报