AJAX 在这里 - 第 3 部分:自动完成文本框






4.79/5 (32投票s)
2005年5月2日
8分钟阅读

498314

3745
一个自定义的 AJAX - ASP.NET 控件。
引言
如果您一直在关注本系列文章,那么您知道在第 1 部分中,我们简要讨论了异步 JavaScript 和 XML,简称 AJAX。我们创建了 CallBackObject
来帮助简化回调的启动过程。在第 2 部分中,我们研究了如何将 CallBackObject
与 ASP.NET 控件和事件集成。现在,在第 3 部分,我们将把这些知识付诸实践,并利用我们迄今为止所学的一切来创建一个自定义 ASP.NET 控件。正如您可能猜到的,该控件就是自动完成文本框 (ACTB
)。
在开始讲解精彩内容之前,我想说这个控件还有很大的改进空间。功能、设计时集成和易用性都可以得到极大的改善。这部分留给您!
好东西
在讨论创建控件之前,我想向您展示该控件的易用性。我们将从列表 1 和列表 2 (Actb.aspx 和 Actb.aspx.cs) 开始。在您平息了压倒性的兴奋之后,我们将检查 ACTB 列表 3 (AutoCompleteTextBox.js) 的 JavaScript 部分和 ASP.NET 部分列表 4 (AutoCompleteTextBox.cs)。
Actb.aspx
此页面非常简单。我们正在为用户设置一些输入字段。你们中的大多数人可能会注意到并非所有国家都使用邮政编码。我将其作为演示,说明如何根据其他字段的值来自定义自动完成文本框的结果。(稍后将详细介绍。)要使用我们的自定义控件,我们必须在页面中注册程序集并为其指定一个标签前缀。我已谦虚地使用了我的首字母缩写作为标签前缀,但您可以选择任何您想要的名称。我们还指定了控件可以找到的命名空间和程序集名称。
<%@ Register TagPrefix="wcp" Namespace="WCPierce.Web.UI.WebControls"
Assembly="WCPierce.Web" %>
在此之后,唯一有趣的 HTML 部分是 ACTB
的实际声明。
<wcp:AutoCompleteTextBox runat="server" id="actbCountry"
OnTextChanged="actbCountry_TextChanged" ListItemCssClass="ListItem"
ListItemHoverCssClass="ListItemHover" />
此声明几乎与普通的 ASP TextBox
相同。唯一添加的属性是用于美化我们的下拉列表的 ListItemCssClass
和 ListItemHoverCssClass
。这些 CSS 类定义在外部样式表 AutoCompleteTextBox.css 中。
Actb.aspx.cs
当我们检查代码隐藏文件时,事情开始变得稍微有趣一些。首先需要注意的是代码顶部的 using
语句。
. . .
using Microsoft.ApplicationBlocks.Data;
using WCPierce.Web;
using WCPierce.Web.UI.WebControls;
Microsoft 提供的 Data Application Block 用于简化一些数据库访问代码。其他两个 using
指令使我们能够访问 Auto Complete TextBox 和 CallBackHelper
类。
请注意我们在 HTML 中引用的 actbCountry_TextChanged
事件。此函数中的所有内容都包含在 try
/catch
块中,并且任何错误都通过使用 CallBackHelper.HandleError
返回给客户端。
try
{
//. . .
}
catch(Exception ex)
{
CallBackHelper.HandleError( ex );
}
这一点很重要,因为回调期间发生的错误可能非常难以调试(相信我)。有了这段代码和正确的 JavaScript,调试错误就变得轻而易举。
第一步是检查 txtZip
输入字段的内容。如果其中有值,actbCountry
将默认设置为“United States”(抱歉,其他使用邮政编码的国家)。
if( txtZip.Text.Length > 0 )
{
CallBackHelper.Write( "United States" );
}
对于下一部分代码,重要的是要记住,每次用户在 ACTB
中按下按键时,此事件都会触发。因此,如果 Zip 字段为空,我们会获取用户当前在 Country 字段中输入的内容,例如,我们使用“U”,然后在数据库中搜索每个以字母“U”开头的国家。
AutoCompleteTextBox actb = s as AutoCompleteTextBox;
string str = String.Format("SELECT [Text]
FROM Lists
WHERE ListName='Country'
AND [Text] LIKE '{0}%'
ORDER BY [Text]", actb.Text);
所有匹配的国家(乌干达、乌克兰、乌拉圭等)都包含在 SqlDataReader
中。然后,我们将自动完成文本框的 DataSource
属性设置为 SqlDataReader
。此过程与用于绑定到 DropDownList
或 DataGrid
的过程相同。
SqlDataReader sdr = SqlHelper.ExecuteReader(@"Server=(local);
Database=DotNetNuke;
Integrated Security=SSPI;",
CommandType.Text, str);
actb.DataSource = sdr;
actb.DataTextField = "Text";
actb.BindData();
精明的读者会注意到这里唯一的区别是调用了 BindData()
。通常,对于 DataBound
控件,您会调用 DataBind()
。不幸的是,我无法使其正常工作。原因有点复杂,但如果您尝试在禁用了 ViewState 的 DataGrid
中使用自动完成文本框,您就可以看出原因。
就这样!我已经将所有繁重的工作封装在 ACTB
控件中。您现在可以像使用其他数据绑定控件一样使用它。请访问此网站,观看 ACTB
的演示。将 Zip 留空,然后在 Country 框中输入一个字母,瞧,您应该会看到一个带有匹配项的下拉列表,以及列表中第一个国家/地区的自动完成功能。太棒了!
AutoCompleteTextBox.js
在开始讨论这个小宝石之前,我需要感谢那些帮助我实现这个控件的伟大个人
来自上述两篇文章的概念加上 CallBackObject
产生了 ACTB
。这一部分有很多代码,与其逐行讲解(而且我的手指快累了),我只讲解新的部分。
AutoCompleteTextBox.prototype.TextBox_KeyUp = function(oEvent)
{
var iKeyCode = oEvent.keyCode;
if( iKeyCode == 8 )
{
this.Div.innerHTML = '';
this.Div.style.display = 'none';
return;
}
else if( iKeyCode == 16 || iKeyCode == 20 )
{
this.DoAutoSuggest = true;
}
else if (iKeyCode < 32 || (iKeyCode >= 33 && iKeyCode <= 46) ||
(iKeyCode >= 112 && iKeyCode <= 123))
{
return;
}
else
{
this.DoAutoSuggest = true;
}
var txt = this.TextBox.value;
if( txt.length > 0 )
{
this.Cbo.DoCallBack(this.TextBox.name, txt);
}
else
{
this.Div.innerHTML = '';
this.Div.style.display = 'none';
this.Cbo.AbortCallBack();
}
}
每当在 ACTB
中按下按键时,此事件就会触发。我们进行一些检查,查看按下了哪个键以及文本框中是否有任何数据。如果有,我们就调用 Cbo.DoCallBack
。如您在第 1 部分中所知,这就是启动服务器端请求的操作。
当服务器端请求完成时,我们的方法 AutoCompleteTextBox.prototype.Cbo_Complete
将被执行,我们处理结果。
AutoCompleteTextBox.prototype.Cbo_Complete = function(responseText, responseXML)
{
while ( this.Div.hasChildNodes() )
this.Div.removeChild(this.Div.firstChild);
// get all the matching strings from the server response
var aStr = responseText.split('\n');
// add each string to the popup-div
var i, n = aStr.length;
if( n > 0 )
{
for ( i = 0; i < n; i++ )
{
var oDiv = document.createElement('div');
this.Div.appendChild(oDiv);
try
{
oDiv.innerHTML = aStr[i];
}
catch(e)
{
this.Cbo_Error('405','Error','Text returned from Call Back was invalid');
return;
}
oDiv.noWrap = true;
oDiv.style.width = '100%';
oDiv.className = this.ListItemClass;
oDiv.onmousedown = AutoCompleteTextBox.prototype.Div_MouseDown;
oDiv.onmouseover = AutoCompleteTextBox.prototype.Div_MouseOver;
oDiv.onmouseout = AutoCompleteTextBox.prototype.Div_MouseOut;
oDiv.AutoCompleteTextBox = this;
}
this.Div.style.display = 'block';
if( this.DoAutoSuggest == true )
this.AutoSuggest( aStr );
}
else
{
this.Div.innerHTML = '';
this.Div.style.display='none';
}
}
从服务器返回的数据是以换行符分隔的国家/地区名称列表。在我们删除下拉列表中任何当前条目后,我们将返回值拆分为国家/地区名称数组。然后,我们遍历数组,将每个国家/地区添加到下拉列表中,分配一些事件,并设置任何样式。然后向用户显示填充的列表,并调用 AutoSuggest
方法,以执行输入预判功能,并将列表中的第一个条目放入文本框并选择正确的字符。
如果我在此部分一带而过,我很抱歉,但本节开头引用的文章解释了剩余的代码。
AutoCompleteTextBox.cs
我已尽力在控件本身的注释中做到详尽。我们将重点介绍控件的要点,而不是逐行讲解(而且我的手指快累了)。
首先需要注意的是,AutoComplete TextBox 继承自内置的 ASP TextBox
。
public class AutoCompleteTextBox : System.Web.UI.WebControls.TextBox
这为我们节省了大量工作。ACTB
有许多附加属性,用于指定一些 CSS 信息和用于客户端部分使用的 JavaScript 文件的路径。Render
方法是开始的地方。
protected override void Render(HtmlTextWriter output)
{
string uId = this.UniqueID;
string newUid = uId.Replace(":", "_");
string divId = newUid + "_Div";
string jsId = newUid + "_Js";
StringBuilder acScript = new StringBuilder();
acScript.Append("<script type='\"text/javascript\"'>");
acScript.AppendFormat("var {0} = new AutoCompleteTextBox('{1}','{2}');
{0}.ListItemClass='{3}';
{0}.ListItemHoverClass='{4}';", jsId, newUid, divId,
this.ListItemCssClass, this.ListItemHoverCssClass);
acScript.Append("</script>");
Page.RegisterStartupScript(newUid, acScript.ToString());
base.Attributes.Add("AutoComplete", "False");
base.Render(output);
output.Write(String.Format("<DIV id={0}></DIV>", divId));
}
为了让客户端 JavaScript 生效,它需要引用将充当 ACTB
的文本框,以及将充当控件下拉部分的 <DIV>
标签。ASP.NET 控件有一个 ID,通常在您的代码中使用,还有一个 UniqueID
,它是在页面级别控件的唯一标识符。有时 ControlID
和 UniqueID
相同,但在使用用户控件、服务器控件或 DataList
控件时,您会遇到麻烦。因此,我们获取对 ACTB
UniqueID
的引用,并为我们的 <DIV>
标签以及动态 JavaScript 代码创建 ID。
string uId = this.UniqueID;
string newUid = uId.Replace(":", "_");
string divId = newUid + "_Div";
string jsId = newUid + "_Js";
接下来,我们动态创建客户端所需的 JavaScript,以创建自动完成文本框。我们使用 CSS 属性初始化 JavaScript 对象,并让 ASP.NET 使用 RegisterStartupScript
将其放置在页面上的正确位置。
StringBuilder acScript = new StringBuilder();
acScript.Append("<script type='\"text/javascript\"'>");
acScript.AppendFormat("var {0} = new AutoCompleteTextBox('{1}','{2}');
{0}.ListItemClass='{3}';
{0}.ListItemHoverClass='{4}';", jsId, newUid, divId,
this.ListItemCssClass, this.ListItemHoverCssClass);
acScript.Append("</script>");
Page.RegisterStartupScript(newUid, acScript.ToString());
最后,我们让我们的 base
类 (TextBox
) 自身进行渲染,然后渲染下拉功能所需的 <DIV>
标签。
base.Attributes.Add("AutoComplete", "False");
base.Render(output);
output.Write(String.Format("<DIV id={0}></DIV>", divId));
我们还重写了 OnTextChanged
事件。我们只希望在回调期间发生此事件,并且仅当我们的控件是事件的目标时。
protected override void OnTextChanged(EventArgs e)
{
if( Page.Request.Params["__EVENTTARGET"] ==
this.UniqueID && CallBackHelper.IsCallBack )
{
base.OnTextChanged( e );
}
}
最后需要解决的是 AutoPostBack
和 DataBind
。显然,如果启用了 AutoPostBack
,我们将破坏 AJAX 的目的。DataBind
方法必须被重写以消除其功能。如果您中的任何一个人弄清楚如何使其工作,请告诉我。目前,开发人员必须调用 BindData
。
public override void DataBind()
{
// Do Nothing
}
public override bool AutoPostBack
{
get { return false; }
}
public virtual void BindData()
{
this.OnDataBinding(EventArgs.Empty);
}
结论
最后,我们劳动的成果交付了一个简洁的小控件,为 ASP.NET 网站带来了新的功能。有很多代码需要消化,我鼓励您下载源代码并进行尝试。我希望我奠定了基础,并为未来的开发播下了想法的种子。这项技术潜力巨大,终于受到了应有的关注。
列表 1 - Actb.aspx
<%@ Register TagPrefix="wcp" Namespace="WCPierce.Web.UI.WebControls"
Assembly="WCPierce.Web" %>
<%@ Page language="c#" Codebehind="Actb.aspx.cs" AutoEventWireup="false"
Inherits="TestWeb.Actb" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>AutoCompleteTextBox Example</title>
<meta name=vs_defaultClientScript content="JavaScript">
<meta name=vs_targetSchema
content="http://schemas.microsoft.com/intellisense/ie5">
<link href="Css/AutoCompleteTextBox.css"
type="text/css" rel="stylesheet" />
</HEAD>
<body>
<form id="Form1" method="post" runat="server">
<table>
<tr>
<td colspan="2">Please enter your informaiton below.</td>
</tr>
<tr>
<td>Name:</td>
<td><asp:TextBox Runat="server" ID="txtName" /></td>
</tr>
<tr>
<td>Zip:</td>
<td><asp:TextBox Runat="server" ID="txtZip" /></td>
</tr>
<tr>
<td>Country:</td>
<td>
<wcp:AutoCompleteTextBox runat="server" id="actbCountry"
OnTextChanged="actbCountry_TextChanged"
ListItemCssClass="ListItem"
ListItemHoverCssClass="ListItemHover" />
</td>
</tr>
</table>
</form>
</body>
</HTML>
列表 2 - Actb.aspx.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using Microsoft.ApplicationBlocks.Data;
using WCPierce.Web;
using WCPierce.Web.UI.WebControls;
namespace TestWeb
{
public class Actb : System.Web.UI.Page
{
protected System.Web.UI.WebControls.TextBox txtName;
protected WCPierce.Web.UI.WebControls.AutoCompleteTextBox actbCountry;
protected System.Web.UI.WebControls.TextBox txtZip;
private void Page_Load(object sender, System.EventArgs e) { }
protected void actbCountry_TextChanged(object s, EventArgs e)
{
try
{
if( txtZip.Text.Length > 0 )
{
CallBackHelper.Write( "United States" );
}
else
{
AutoCompleteTextBox actb = s as AutoCompleteTextBox;
string str = String.Format("SELECT [Text]
FROM Lists
WHERE ListName='Country'
AND [Text] LIKE '{0}%'
ORDER BY [Text]", actb.Text);
SqlDataReader sdr = SqlHelper.ExecuteReader(@"Server=(local);
Database=DotNetNuke;
Integrated Security=SSPI;",
CommandType.Text, str);
actb.DataSource = sdr;
actb.DataTextField = "Text";
actb.BindData();
}
}
catch(Exception ex)
{
CallBackHelper.HandleError( ex );
}
}
#region Web Form Designer generated code
}
}
列表 3 - AutoCompleteTextBox.js
function AutoCompleteTextBox(TextBoxId, DivId, DivClass)
{
// initialize member variables
var oThis = this;
var oText = document.getElementById(TextBoxId);
var oDiv = document.getElementById(DivId);
this.TextBox = oText;
this.Div = oDiv;
// CallBackObject + Event Handlers
this.Cbo = new CallBackObject();
this.Cbo.OnComplete = function(responseText,responseXML)
{oThis.Cbo_Complete(responseText,responseXML);};
this.Cbo.OnError = function(status,statusText,responseText)
{oThis.Cbo_Error(status,statusText,responseText);};
// attach handlers to the TextBox
oText.AutoCompleteTextBox = this;
oText.onkeyup = AutoCompleteTextBox.prototype.OnKeyUp;
oText.onblur = AutoCompleteTextBox.prototype.OnBlur;
// align the drop down div
var c = GetCoords(oText);
var n = oText.style.pixelHeight;
if( !n )
{
n = 25;
}
else
{
n += 2;
}
oDiv.style.left = c.x;
oDiv.style.top = c.y + n;
oDiv.style.display = 'none';
oDiv.style.position = 'absolute';
// Set some default styles
if( DivClass )
oDiv.className = DivClass;
else
{
oDiv.style.border = '1';
oDiv.style.borderColor = 'black';
oDiv.style.borderStyle = 'solid';
oDiv.style.backgroundColor = 'white';
oDiv.style.padding = '2';
}
}
AutoCompleteTextBox.prototype.DoAutoSuggest = false;
AutoCompleteTextBox.prototype.ListItemClass = '';
AutoCompleteTextBox.prototype.ListItemHoverClass = '';
// TextBox OnBlur
AutoCompleteTextBox.prototype.OnBlur = function()
{
this.AutoCompleteTextBox.TextBox_Blur();
}
AutoCompleteTextBox.prototype.TextBox_Blur = function()
{
this.Div.style.display='none';
}
// TextBox OnKeyUp
AutoCompleteTextBox.prototype.OnKeyUp = function(oEvent)
{
//check for the proper location of the event object
if (!oEvent)
{
oEvent = window.event;
}
this.AutoCompleteTextBox.TextBox_KeyUp(oEvent);
}
AutoCompleteTextBox.prototype.TextBox_KeyUp = function(oEvent)
{
var iKeyCode = oEvent.keyCode;
if( iKeyCode == 8 )
{
this.Div.innerHTML = '';
this.Div.style.display = 'none';
return;
}
else if( iKeyCode == 16 || iKeyCode == 20 )
{
this.DoAutoSuggest = true;
}
else if (iKeyCode < 32 || (iKeyCode >= 33 && iKeyCode <= 46) ||
(iKeyCode >= 112 && iKeyCode <= 123))
{
return;
}
else
{
this.DoAutoSuggest = true;
}
var txt = this.TextBox.value;
if( txt.length > 0 )
{
this.Cbo.DoCallBack(this.TextBox.name, txt);
}
else
{
this.Div.innerHTML = '';
this.Div.style.display = 'none';
this.Cbo.AbortCallBack();
}
}
AutoCompleteTextBox.prototype.Cbo_Complete =
function(responseText, responseXML)
{
while ( this.Div.hasChildNodes() )
this.Div.removeChild(this.Div.firstChild);
// get all the matching strings from the server response
var aStr = responseText.split('\n');
// add each string to the popup-div
var i, n = aStr.length;
if( n > 0 )
{
for ( i = 0; i < n; i++ )
{
var oDiv = document.createElement('div');
this.Div.appendChild(oDiv);
try
{
oDiv.innerHTML = aStr[i];
}
catch(e)
{
this.Cbo_Error('405','Error',
'Text returned from Call Back was invalid');
return;
}
oDiv.noWrap = true;
oDiv.style.width = '100%';
oDiv.className = this.ListItemClass;
oDiv.onmousedown = AutoCompleteTextBox.prototype.Div_MouseDown;
oDiv.onmouseover = AutoCompleteTextBox.prototype.Div_MouseOver;
oDiv.onmouseout = AutoCompleteTextBox.prototype.Div_MouseOut;
oDiv.AutoCompleteTextBox = this;
}
this.Div.style.display = 'block';
if( this.DoAutoSuggest == true )
this.AutoSuggest( aStr );
}
else
{
this.Div.innerHTML = '';
this.Div.style.display='none';
}
}
AutoCompleteTextBox.prototype.Cbo_Error =
function(status, statusText, responseText)
{
alert('CallBackObject Error: status=' + status + '\nstatusText=' +
statusText + '\n' + responseText);
}
AutoCompleteTextBox.prototype.Div_MouseDown = function()
{
this.AutoCompleteTextBox.TextBox.value = this.innerHTML;
}
AutoCompleteTextBox.prototype.Div_MouseOver = function()
{
if( this.AutoCompleteTextBox.ListItemHoverClass.length > 0 )
this.className = this.AutoCompleteTextBox.ListItemHoverClass;
else
{
this.style.backgroundColor = 'black';
this.style.color = 'white';
}
}
AutoCompleteTextBox.prototype.Div_MouseOut = function()
{
if( this.AutoCompleteTextBox.ListItemClass.length > 0 )
this.className = this.AutoCompleteTextBox.ListItemClass;
else
{
this.style.backgroundColor = 'white';
this.style.color = 'black';
}
}
AutoCompleteTextBox.prototype.AutoSuggest =
function(aSuggestions /*:array*/)
{
if (aSuggestions.length > 0)
{
this.TypeAhead(aSuggestions[0]);
}
}
AutoCompleteTextBox.prototype.TypeAhead =
function( sSuggestion /*:string*/)
{
if( this.TextBox.createTextRange || this.TextBox.setSelectionRange)
{
var iLen = this.TextBox.value.length;
this.TextBox.value = sSuggestion;
this.SelectRange(iLen, sSuggestion.length);
}
}
AutoCompleteTextBox.prototype.SelectRange =
function (iStart /*:int*/, iLength /*:int*/)
{
//use text ranges for Internet Explorer
if (this.TextBox.createTextRange)
{
var oRange = this.TextBox.createTextRange();
oRange.moveStart("character", iStart);
oRange.moveEnd("character", iLength - this.TextBox.value.length);
oRange.select();
//use setSelectionRange() for Mozilla
}
else if (this.TextBox.setSelectionRange)
{
this.TextBox.setSelectionRange(iStart, iLength);
}
//set focus back to the textbox
this.TextBox.focus();
}
function GetCoords(obj /*:object*/)
{
var newObj = new Object();
newObj.x = obj.offsetLeft;
newObj.y = obj.offsetTop;
theParent = obj.offsetParent;
while(theParent != null)
{
newObj.y += theParent.offsetTop;
newObj.x += theParent.offsetLeft;
theParent = theParent.offsetParent;
}
return newObj;
}
列表 4 - AutoCompleteTextBox.cs
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using WCPierce.Web.UI;
[assembly:TagPrefix("WCPierce.Web.UI.WebControls", "wcp")]
namespace WCPierce.Web.UI.WebControls
{
/// <SUMMARY>
/// AutoCompleteTextBox is similar to the WinForm
/// ComboBox control. As the
/// user types into the box, the enter is
/// "auto-completed" based on values
/// databound to the TextBox by the developer.
/// </SUMMARY>
[DefaultProperty("Text"),
ToolboxData("<{0}:AutoCompleteTextBox runat=server>")]
public class AutoCompleteTextBox : System.Web.UI.WebControls.TextBox
{
#region Member Variables
/// <SUMMARY>
/// The (relative) path to the AutoCompleteTextBox JavaScript file.
/// </SUMMARY>
private string _scriptPath = string.Empty;
/// <SUMMARY>
/// For using databinding with your AutoCompleteTextBox
/// </SUMMARY>
private object _dataSource = null;
/// <SUMMARY>
/// Data returned to the client is in
/// the form of "entry"-newline-
/// "entry"-newline...If you wanted to get
/// cute, we could return in an XML
/// format.
/// </SUMMARY>
private static readonly string _FormatString = "{0}\n";
/// <SUMMARY>
/// If a ScriptPath isn't specified, check the
/// web.config file for the
/// following key.
/// </SUMMARY>
private static readonly string _ScriptPath =
"AutoCompleteTextBox.ScriptPath";
/// <SUMMARY>
/// CSS Class name for the list item of the dropdownlist.
/// </SUMMARY>
private string _listItemCssClass = string.Empty;
/// <SUMMARY>
/// CSS Class name for the "hover" effect of
/// the list item of the dropdownlist.
/// </SUMMARY>
private string _listItemHoverCssClass = string.Empty;
#endregion
#region Public Properties
/// <SUMMARY>
/// The path to the AutoComplete.js file.
/// If you leave it blank, it will
/// automatically look in the web.config
/// for the value under the key
/// "AutoCompleteTextBox.ScriptPath".
/// Should be a path relative to the
/// application root i.e. "~\scripts\AutoCompleteTextBox.js".
/// </SUMMARY>
public string ScriptPath
{
get
{
if( _scriptPath != string.Empty )
return ResolveUrl(_scriptPath);
try
{
return
ResolveUrl(System.Configuration.ConfigurationSettings.AppSettings[
AutoCompleteTextBox._ScriptPath]);
}
catch
{
return null;
}
}
set { _scriptPath = value; }
}
/// <SUMMARY>
/// CSS Class name for the list item of the dropdownlist.
/// </SUMMARY>
public string ListItemCssClass
{
get { return _listItemCssClass; }
set { _listItemCssClass = value; }
}
/// <SUMMARY>
/// CSS Class name for the "hover" effect of the
/// list item of the dropdownlist.
/// </SUMMARY>
public string ListItemHoverCssClass
{
get { return _listItemHoverCssClass; }
set { _listItemHoverCssClass = value; }
}
/// <SUMMARY>
/// For use with databinding.
/// </SUMMARY>
[Bindable(true),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
DefaultValue((string) null)]
public virtual object DataSource
{
get
{
return _dataSource;
}
set
{
if (((value != null) && !(value is IListSource)) &&
!(value is IEnumerable))
{
throw new ArgumentException("Invalid_DataSource_Type: " +
this.ID);
}
_dataSource = value;
}
}
/// <SUMMARY>
/// For use with databinding.
/// </SUMMARY>
public virtual string DataTextField
{
get
{
object o = this.ViewState["DataTextField"];
if (o != null)
{
return (string)o;
}
return string.Empty;
}
set
{
this.ViewState["DataTextField"] = value;
}
}
/// <SUMMARY>
/// For use with databinding.
/// </SUMMARY>
public virtual string DataTextFormatString
{
get
{
object o = this.ViewState["DataTextFormatString"];
if (o != null)
{
return (string)o;
}
return string.Empty;
}
set
{
this.ViewState["DataTextFormatString"] = value;
}
}
/// <SUMMARY>
/// For use with databinding.
/// </SUMMARY>
[DefaultValue("")]
public virtual string DataMember
{
get
{
object o = this.ViewState["DataMember"];
if (o != null)
{
return (string)o;
}
return string.Empty;
}
set
{
this.ViewState["DataMember"] = value;
}
}
#endregion
#region Overrides
/// <SUMMARY>
/// Render this control to the output parameter specified.
/// </SUMMARY>
/// <PARAM name="output">The HTML writer
/// to write out to.</PARAM>
protected override void Render(HtmlTextWriter output)
{
string uId = this.UniqueID;
string newUid = uId.Replace(":", "_");
string divId = newUid + "_Div";
string jsId = newUid + "_Js";
StringBuilder acScript = new StringBuilder();
acScript.Append("<script type='\"text/javascript\"'>");
acScript.AppendFormat("var {0} =
new AutoCompleteTextBox('{1}','{2}');
{0}.ListItemClass='{3}';
{0}.ListItemHoverClass='{4}';", jsId, newUid, divId,
this.ListItemCssClass, this.ListItemHoverCssClass);
acScript.Append("</script>");
Page.RegisterStartupScript(newUid, acScript.ToString());
base.Attributes.Add("AutoComplete", "False");
base.Render(output);
output.Write(String.Format("<DIV id={0}></DIV>", divId));
}
/// <SUMMARY>
/// Register our common scripts and do default PreRendering.
/// </SUMMARY>
/// <PARAM name="e"></PARAM>
protected override void OnPreRender(EventArgs e)
{
this._RegisterCommonScripts();
base.OnPreRender(e);
}
/// <SUMMARY>
/// Only fire the OnTextChanged event if
/// this control is the target and it
/// is a Call Back
/// </SUMMARY>
/// <PARAM name="e"></PARAM>
protected override void OnTextChanged(EventArgs e)
{
if( Page.Request.Params["__EVENTTARGET"] ==
this.UniqueID && CallBackHelper.IsCallBack )
{
base.OnTextChanged( e );
}
}
/// <SUMMARY>
/// The original idea was to have the
/// Auto Complete Text Box behave like a
/// normal Data Bindable control.
/// But, alas, I couldn't figure that out.
/// Thank you M$ for the databinding code.
/// </SUMMARY>
/// <PARAM name="e"></PARAM>
protected override void OnDataBinding(EventArgs e)
{
base.OnDataBinding(e);
IEnumerable ie =
DataSourceHelper.GetResolvedDataSource(this.DataSource,
this.DataMember);
StringBuilder sb = new StringBuilder();
if( ie != null )
{
bool useTextField = false;
bool useFormatString = false;
string textField = DataTextField;
string formatString = DataTextFormatString;
if( textField.Length != 0 )
{
useTextField = true;
}
if( formatString.Length != 0 )
{
useFormatString = true;
}
foreach( object o in ie )
{
if( useTextField )
{
if( textField.Length > 0)
{
sb.AppendFormat(AutoCompleteTextBox._FormatString,
DataBinder.GetPropertyValue(o, textField, formatString));
}
}
else
{
if( useFormatString )
{
sb.AppendFormat( AutoCompleteTextBox._FormatString,
string.Format(formatString, o) );
}
else
{
sb.AppendFormat(AutoCompleteTextBox._FormatString,
o.ToString());
}
} // useTextField
} // foreach
} // ie != null
// Remove trailing '\n'
if( sb.Length > 1 )
sb.Remove(sb.Length-1, 1);
CallBackHelper.Write( sb.ToString() );
}
/// <SUMMARY>
/// Perhaps in the future, I will figure out
/// how to make this work. Before
/// you email me with the answer please try
/// using an AutoCompleteTextBox in
/// a DataGrid with ViewState disabled.
/// </SUMMARY>
public override void DataBind()
{
// Do Nothing
}
/// <SUMMARY>
/// What's the point if the developer turns on AutoPostBack?
/// </SUMMARY>
public override bool AutoPostBack
{
get { return false; }
}
#endregion
#region Public Methods
/// <SUMMARY>
/// For now, Developer's must call this method to bind to their
/// Auto Complete Text Box.
/// </SUMMARY>
public virtual void BindData()
{
this.OnDataBinding(EventArgs.Empty);
}
#endregion
#region Helper Methods
/// <SUMMARY>
/// Add a reference to the JavaScript, but only once per page.
/// </SUMMARY>
private void _RegisterCommonScripts()
{
if (!this.Page.IsClientScriptBlockRegistered("AutoCompleteTextBox"))
{
StringBuilder script = new StringBuilder();
script.AppendFormat("<script src="{0}" type=text/javascript></script>",
this.ScriptPath);
this.Page.RegisterClientScriptBlock("AutoCompleteTextBox",
script.ToString());
}
}
#endregion
}
}
历史
- 2005-05-02
- 初始发布。