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中)

初寫博客,還請大牛們多多指教


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM