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

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

 

扩展代码如下


[转]Asp.Net MVC 扩展联想控件_css[转]Asp.Net MVC 扩展联想控件_数据_02View Code


[转]Asp.Net MVC 扩展联想控件_html_03

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
}
}


[转]Asp.Net MVC 扩展联想控件_html_03


 

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

Controller代码如下:


[转]Asp.Net MVC 扩展联想控件_css[转]Asp.Net MVC 扩展联想控件_数据_02View Code


[转]Asp.Net MVC 扩展联想控件_html_03

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
}


[转]Asp.Net MVC 扩展联想控件_html_03


 

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


[转]Asp.Net MVC 扩展联想控件_css[转]Asp.Net MVC 扩展联想控件_数据_02View 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)


[转]Asp.Net MVC 扩展联想控件_css[转]Asp.Net MVC 扩展联想控件_数据_02View Code


[转]Asp.Net MVC 扩展联想控件_html_03

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


[转]Asp.Net MVC 扩展联想控件_html_03


结果演示一:简单单列

[转]Asp.Net MVC 扩展联想控件_html_15

结果演示二:带表头单列

[转]Asp.Net MVC 扩展联想控件_css_16

结果演示三:带表头双列

[转]Asp.Net MVC 扩展联想控件_数据_17

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

[转]Asp.Net MVC 扩展联想控件_css_18