Introduction
I had a requirement to be able to quickly record call history for a customer when they telephoned; which was to be implemented in a footer of a .NET master page; thus appear on every page of the website. Since there were over 500 customers in the system, a dropdown list would have been too large, and it would have been too complex to introduce some sort of search-engine in the footer of a master page.
I came across a CodeProject article written by zichun here. It also includes a demo which did exactly what I wanted; but was implemented in JavaScript and required the entries for the autocomplete dropdown to be pre-specified in script, which was not quite ideal for what I needed: a dynamic list of strings from a database.
This article looks at how I implemented this control as a .NET user control which can be drag-dropped onto any ASPX web form where it is needed and can have its items list dynamically and easily set programmatically.
Credits
Thank you to zichun of CodeProject for the original article referenced above. All the JavaScript code is his; and is excellent.
Implementation
The first thing to do was to include the two JavaScript files in my project. For more information about these files, you can see the original article this was based on, here. I created an App_Javascript folder in the root of my project (just to follow the .NET naming convention) and included actb.js and common.js; both of which were written by zychun and I take no credit for.
Next, I created a /Controls folder and added into it a new .NET user control called AutoCompleteTextBox.ascx. The first thing to do was create the markup. All that was needed was simply a TextBox
control (where the user will enter what they want to auto-complete) and a Literal
which was used to output the script tags containing the items which would appear as auto-complete options. Thus, the content of AutoCompleteTextBox.ascx was:
<asp:TextBox id="txtTheTextbox" runat="server" />
<asp:Literal id="litTheJS" runat="server" />
The next thing to do was to write some code in Page_Init
of this user control which wrote out references to common.js and actb.js in the head
tag of the page if they did not exist already. This means that these references will only appear in pages which use this control, and they will never be accidentally missed out. To do this, I simply checked for the existence of a Literal
in the page header; if it did not exist, then I created it and added it as follows:
Note that this relies on the files being in the App_Javascript folder in your application root, change as appropriate:
Protected Sub Page_Init(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Init
If IsNothing(Me.Page.Header.FindControl("litAutocompleteTextBoxSrc")) Then
Dim lit As New Literal
lit.ID = "litAutocompleteTextBoxSrc"
lit.Text = vbCrLf + String.Format("<script language="
" type='text/javascript' src='{0}'></script>", _
Server.MapPath("~/App_Javascript/actb.js"))
lit.Text += vbCrLf + String.Format("<script language="
" type='text/javascript' src='{0}'></script>", _
Server.MapPath("~/App_Javascript/common.js"))
lit.Text += vbCrLf
Me.Page.Header.Controls.Add(lit)
End If
End Sub
With the .js files now included in the header tags of the rendered ASPX page, the next job was simply to provide two properties to get/set the text and the width of the TextBox
control:
Public Property Text() As String
Get
Return Me.txtTheTextbox.Text
End Get
Set(ByVal value As String)
Me.txtTheTextbox.Text = value
End Set
End Property
Public Property Width() As Unit
Get
Return Me.txtTheTextbox.Width
End Get
Set(ByVal value As Unit)
Me.txtTheTextbox.Width = value
End Set
End Property
Now, I added a write-only property named Items
which is an array of strings. I implemented this as WriteOnly
because I wanted it to be possible to change or set the items at any time. However, I saw no real reason to ever need to read out the string array of items, so implemented this as write-only so that I would never try to read them and get confused as to why I got a null/empty string array!
The trick is that this property converts the string array object into a long string which the JavaScript uses as its items array. So, a string array containing the words apple, orange, and banana gets converted into the long string 'apple','orange','banana' including all the ' marks.
This long string is then stored into a ViewState variable which is used later when rendering out the JavaScript:
Public WriteOnly Property Items() As String()
Set(ByVal value As String())
Dim ItemsString As String = String.Empty
For Each s As String In value
ItemsString += "'" & s & "',"
Next
ItemsString = ItemsString.Substring(0, ItemsString.Length - 1)
ViewState("TheItems") = ItemsString
End Set
End Property
And finally, my last trick is to set the contents of the literal we defined in the markup to be the javascript which is required to make the auto-complete dropdown list work. I did this in the Page_Load event of the control; and simply used the contents of the ViewState variable set using the .Items
property created above. This also calls the actb
function (defined in one of the JavaScript files) to "attach" the dropdown list to the TextBox
using the TextBox
's ClientID
:
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
Me.litTheJS.Text = String.Format("<script type='text/javascript'> " &_
"var customarray = new Array({1}); " &_
"actb(document.getElementById('{0}'),customarray); </script>",
Me.txtTheTextbox.ClientID, ViewState("TheItems"))
End Sub
And that's it! The control is now ready for use.
Using the Control
To use the control, simply add a reference to it in a page's markup (such as .aspx or .master) as you would any custom user control. (This can also be done by dragging the control from the Solution Explorer onto the page in Design view, and Visual Studio will automatically write the markup for you):
<%@ Register Src="~/Controls/AutoCompleteTextBox.ascx"
TagName="AutoCompleteTextBox" TagPrefix="bgs" %>
And then when you want the TextBox
to appear:
<bgs:AutoCompleteTextBox ID="txtAutoComplete" runat="server" Width="300px" />
Finally, all that is left is to set the .Items
property of the control anywhere you like in code to set the auto-complete dropdown content of the box, and the rest of it just works, thanks to zichun's JavaScript:
Me.txtAutoComplete.Items = New String() {"Apple", "Orange", "Banana"}
Known Issues
I noticed that originally this worked perfectly when working on the web server machine, but failed when looking at the application on the server from a remote machine. However, Server.MapPath("~")
returns a link to c:\inetpub\wwwroot\... - which obviously when connecting to the application remotely caused a JavaScript "object required" error because the remote machine had no such file - but worked perfectly on the server. The answer was to fix the path a bit in Page_Init
of the control.
Feedback
I would welcome any feedback on this control; although I cannot promise to be able to help with any queries. This works perfectly for my needs; and whilst I will try to help where I can, I am very busy at the moment!
Thanks
I've said it a lot, but this control could not have been written without the JavaScript and article by zychun, here.