ASP.NET(C#) : Autocomplete TextBox - Part 1 (From Scratch)
The source code for the following post can be downloaded
here.
Back in 2007 I had the wonderful privilege to work at
Web Africa, one of the largest
internet service providers in South Africa.
One of my PHP projects
(or at least a small part of the project) involved creating an autocomplete textbox when searching for
domains located within the company mysql database.
If memory serves me correctly
(it probably doesn't) thats around the
same time google introduced google suggests at google labs - something that is
standard to its search these days.
(subtle attempt at humour, ahhh.... ha ha)
Last month we needed the same functionality in one of our ASP.NET projects at my
current company.
In this post we're going to have a look at how to create our very own autocomplete textbox using
ASP.net (C#)
(with a little bit of javascript of course).
Lets jump into some code...
We can retrieve our autocomplete results via
PageMethods (ajax request, json response), which we
need to enable in our scriptmanager at the top of our page like seen in the following snippet:
<asp:ScriptManager runat="server" ID="scriptmanager" EnablePageMethods="true">
<Scripts>
<asp:ScriptReference Path="~/js/autocomplete.js" />
</Scripts>
</asp:ScriptManager>
The server side method responsible for retrieving data will look something like this
(you will obviously
want to retrieve results via some database - this example is solely for demo purposes)
[WebMethod]
public static string[] GetList(string prefixText, int count)
{
// Dummy data - don't do this
string[] data = new string[] {
"Christoff Trüter", "Eugene Stander",
"Roland Cooper", "Alexander Mehlhorn",
"Derek Campher", "Julie Trüter",
"Hanno Coetzee", "Wayne Kleynhans",
"Pieter Du Plooy", "Pam Nizar" };
return (from p in data
where p.IndexOf(prefixText, StringComparison.OrdinalIgnoreCase) >= 0
select p).Take<String>(count).ToArray();
}
Alternatively results can also be retrieved via webservice, we need to register the service using the scriptmanager -
this makes its methods available to our javascript script.
(retrieving our results using a webservice
is a bit more reusable than the PageMethod option, since PageMethods are only available within the page they're created on)
<asp:ScriptManager runat="server" ID="scriptmanager">
<Services>
<asp:ServiceReference Path="~/MyService.asmx" />
</Services>
<Scripts>
<asp:ScriptReference Path="~/js/autocomplete.js" />
</Scripts>
</asp:ScriptManager>
The webservice code will look something like this:
using System;
using System.Linq;
using System.Web.Services;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class MyService : System.Web.Services.WebService
{
[WebMethod]
public string[] GetList(String prefixText, Int32 count)
{
// Dummy data - don't do this
string[] data = new string[] {
"Christoff Trüter",
"Eugene Stander",
"Roland Cooper",
"Alexander Mehlhorn",
"Derek Campher",
"Julie Trüter",
"Hanno Coetzee",
"Wayne Kleynhans",
"Pieter Du Plooy",
"Pam Nizar" };
return (from p in data
where p.IndexOf(prefixText, StringComparison.OrdinalIgnoreCase) >= 0
select p).Take<String>(count).ToArray();
}
}
Note the ScriptService attribute at the top of the MyService class, its important to set this
attribute in order to make the service useable via our scriptmanager and expose it to javascript.
Okay, now that we've got an idea of where our data will be coming from, lets have a look at the javascript
(js/autocomplete.js) script that will make this all possible.
Within our autocomplete.js file we've got a function called AutoComplete, now this function needs to be
called as soon as the page is done loading - we can do this using the pageLoad function which will
automatically be called by the scriptmanager as soon as the page finished loading, observe:
<script type="text/javascript">
function pageLoad() {
AutoComplete('<%=txtTest.ClientID %>', 10, 500, PageMethods, 'GetList');
}
</script>
<asp:TextBox ID="txtTest" runat="server"></asp:TextBox>
The first argument to the AutoComplete function contains the ID of the textbox we wish to
autocomplete, the second the maximum amount of results to return, third the timeout - how long
the script needs to wait
(in milliseconds) before it attempts to query the server.
The fourth argument is where it gets a little bit more interesting, when using PageMethods an
object named PageMethods becomes available to our scripts, when using a webservice the scriptmanager
will create an object thats got the same name as the class name we gave our webservice.
In this example the object will be named MyService
(if you use namespaces you'll need to include that in your object as well e.g. MyNamespace.MyService), now instead of passing the PageMethods object to
the function, you can alternatively pass the MyService object to the function
(these objects are interchangeable).
The last argument obviously tells the function which method to call e.g. GetList.
Lets dig a bit deeper to see how all of this works.
(js/autocomplete.js)
function AutoComplete(targetId, count, timeout, service, method) {
var timeoutId;
var request;
var target = document.getElementById(targetId);
var list = CreateList(target);
$addHandler(target, "keyup", function() {
if (timeoutId != undefined) {
clearTimeout(timeoutId);
}
if (request != undefined) {
var executor = request.get_executor();
if (executor.get_started()) {
executor.abort();
}
}
timeoutId = setTimeout(function() {
request = service._staticInstance[method](target.value, count, success, function() { });
}, timeout);
});
Notice the $addHandler function, this is function available courtesy of the scriptmanager - since
browsers like IE and FireFox handle events differently, we need a crossbrowser friendly function
to attach events.
Within our keyup eventhandler you will notice a few interesting things:
- We need to minimize the amount of requests to the
server therefore we clear our timer (set using setTimeout) callback with every keyup, if we don't do this every key pressed will do a request to the server like
seen in the following image:
-
We also abort requests already in progress, we ideally only want one active request at a time - especially since we're limited to the amount of
requests in our browsers - if a key is pressed and an active request is found, the script aborts it, like seen in the image
below:
The success function like seen below
(nested within the AutoComplete giving it access to all its "private" fields), gets executed if
the request successfully executes - populating the autcomplete listbox.
function success(result) {
list.options.length = 0; // clear listbox
if (result != '') {
list.setAttribute("size", result.length + 1);
list.style.display = "block";
for (var index = 0; index < result.length; index++) {
list.options.add(new Option(result[index], result[index], false));
}
} else {
list.style.display = "none";
}
}
In the following snippet we attach a handler to the page which assigns the selected item to
the specified target and hides the active listbox.
$addHandler(document, "click", function(e) {
if ((e.srcElement == target) || (e.target == target)) {
return false;
}
list.style.display = "none";
if (list.value != '') {
target.value = list.value;
}
});
}
// Append the autocomplete listbox to the target textbox
function CreateList(sender) {
var div = document.createElement("div");
var list = document.createElement("select");
list.style.position = "absolute";
list.style.display = "none";
div.appendChild(list);
sender.parentNode.insertBefore(div, sender.nextSibling);
return list;
}
Other things one can look at is caching the ajax results in order to free the server up
a bit more etc...
In the next part of this post we're going to have a quick look at how to use an autocomplete
extender control that microsoft created
(which should represent a more mature solution).
Additional Reading
AjaxControlToolkit : Autocomplete
Posted by - Christoff Truter
Date - 2011-03-27 20:50:07
Comments
Post comment