Introduction
This is an AJAX control which enables multiple instances of an auto-complete list using JQuery.
The project uses the Ajax.NET framework.
Background
After reading firefalcon's article (Implementing an Ajax.NET-based Lookup Server Control), I wanted to add several instances of it and also cache the information sent by the server.
Using the code
First of all, we need to implement the functions to be called from the client:
using System;
using System.Collections;
using System.Web;
using Ajax;
namespace AjaxExample
{
public partial class Index : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.search.Attributes.Add("onkeyup",
"Index.GetSearchItems(this.value, GetSearchItems_CallBack);");
Utility.RegisterTypeForAjax(typeof(Index));
Page.ClientScript.RegisterStartupScript(typeof(System.Web.UI.Page),
"SeachAutocomplete", "<script type=\"text/" +
"javaScript\">$('#search').attr('autocomplete', 'off')</script>");
}
[AjaxMethod()]
public ArrayList GetSearchItems(string query)
{
ArrayList items = GetRecords();
ArrayList matchItems = new ArrayList();
if (query != string.Empty)
{
foreach (string item in items)
{
if (item.ToLower().StartsWith(query.ToLower()))
matchItems.Add(item);
}
}
return matchItems;
}
[AjaxMethod()]
public ArrayList GetRecords()
{
ArrayList items = new ArrayList();
items.Add("Ted");
items.Add("Teddy");
items.Add("Mark");
items.Add("Alfred");
return items;
}
}
}
Now, we get onto the fun stuff. We create a custom ASPX control which will automatically call the server function:
public class AjaxLookupOnce : TextBox
{
private string callBackFunction = "";
private string backgroundColor = "#EEE";
private string highlightColor = "#CCC";
private string font = "Verdana";
private string divPadding = "2px";
private string divBorder = "1px solid #CCC";
public string CallBackFunction
{
get { return callBackFunction; }
set
{
callBackFunction = value;
this.Attributes.Add("onkeyup",
"CheckAndShowArray(this);");
}
}
public string BackgroundColor
{
get { return backgroundColor; }
set { backgroundColor = value; }
}
public string HighlightColor
{
get { return highlightColor; }
set { highlightColor = value; }
}
public string DivFont
{
get { return font; }
set { font = value; }
}
public string DivPadding
{
get { return divPadding; }
set { divPadding = value; }
}
public string DivBorder
{
get { return divBorder; }
set { divBorder = value; }
}
}
Notice that when the CallBackFuntion
is set, the textbox will have an onkeyup
attribute with what to do when a key has been pressed.
To initialize the textbox for an auto-complete div
, we need to add the following JavaScript (jquery.js is needed for the following to work):
var current;
var cache = new Array();
function InitializeTextboxEvent(TextboxName, StyleArray)
{
$('#' + TextboxName).blur(hideDiv).attr('autocomplete', 'off');
var index = cache.length;
cache[index] = new Array(4);
cache[index]['Textbox'] = '#' + TextboxName;
cache[index]['ResultDiv'] = createDiv(TextboxName, StyleArray);
cache[index]['StyleArray'] = StyleArray;
cache[index]['ResultArray'] = null;
current = cache[cache.length];
}
function createDiv(TextboxID, StyleArray)
{
divID = TextboxID + '_result';
if ($('#' + divID).length == 0) {
$('body').append($('<div></div>').attr('id', divID));
$('#' + divID).css({
'background': StyleArray['DIV_BG_COLOR'],
'font-family' : StyleArray['DIV_FONT'],
'padding' : StyleArray['DIV_PADDING'],
'border': StyleArray['DIV_BORDER'],
'width': $('#' + TextboxID).width(),
'font-size' : '90%',
'padding' : '2px 2px 2px 2px',
'position' : 'absolute',
'left': $('#' + TextboxID).offset().left + 'px',
'top': ($('#' + TextboxID).offset().top + $('#' +
TextboxID)[0].offsetHeight) + 'px',
'visibility' : 'hidden',
'z-index' : 10000
});
}
return '#' + divID;
}
To call the InitializeTextboxEvent()
, we need to do so from the new custom control we made earlier. So to that class, we add:
protected override void Render(HtmlTextWriter writer)
{
if (callBackFunction != string.Empty)
{
base.Render(writer);
string initialize = string.Format("<script type=\"text/javaScript\">" +
Environment.NewLine +
"var Style = new Array(5);" + Environment.NewLine +
"Style['DIV_BG_COLOR'] = '{0}';" + Environment.NewLine +
"Style['DIV_HIGHLIGHT_COLOR'] = '{1}';" + Environment.NewLine +
"Style['DIV_FONT'] = '{2}';" + Environment.NewLine +
"Style['DIV_PADDING'] = '{3}';" + Environment.NewLine +
"Style['DIV_BORDER'] = '{4}';" + Environment.NewLine +
"InitializeTextboxEvent('{5}', Style);" + Environment.NewLine +
callBackFunction + "(CacheArray);" + Environment.NewLine +
"</script>", BackgroundColor, HighlightColor,
DivFont, DivPadding, DivBorder, this.ClientID);
Page.ClientScript.RegisterStartupScript(typeof(Page), "Register_" +
this.ClientID, initialize);
}
}
In this function, we add the style elements declared in the custom control, and call the InitializeTextboxEvent
function with the ID of the control and the style array.
We also cache the callback function in the CacheArray
function, as shown below:
function CacheArray(response)
{
SetCurrentArray(response.value);
}
function SetCurrentArray(response)
{
cache[cache.length - 1]['ResultArray'] = response;
}
Remember the onkeyup
function we specified earlier when CallBackFunction
was defined? Well now, it is time to create it.
function CheckAndShowArray(e)
{
queryElement = e;
if (!getCurrent())
return false;
var value = e.value;
$(current['ResultDiv']).html('');
if (trim(value).length > 0)
{
for (var i = 0; i < current['ResultArray'].length; i++)
{
if (trim(current['ResultArray'][i].toLowerCase()).startsWith(value.toLowerCase()))
$(current['ResultDiv']).append($('<span></span>').attr('class',
'results').html(current['ResultArray'][i])).append($('<br />'));
}
}
addEvents($(current['ResultDiv']).html().length > 0);
}
function getCurrent()
{
var found = false;
for (var i = 0; i < cache.length && !found; i++)
{
if ($(cache[i]['Textbox'])[0] == queryElement)
{
current = cache[i];
found = true;
}
}
return found;
}
function SetDivVisibility(show)
{
if (getCurrent())
{
if (show)
$(current['ResultDiv']).css({ 'visibility': 'visible' });
else
$(current['ResultDiv']).css({ 'visibility': 'hidden' });
}
}
function hideDiv()
{
SetDivVisibility(false);
}
function addEvents(show)
{
$('.results').css({
'font-weight': 'bold',
'cursor': 'pointer',
'padding': '2px'
}).hover(
function() {
$(this).css({ 'background': current['StyleArray']['DIV_HIGHLIGHT_COLOR'] });
$(current['Textbox']).unbind('blur');
},
function() {
$(this).css({ 'background': current['StyleArray']['DIV_BG_COLOR'] });
$(current['Textbox']).blur(hideDiv);
}).click(
function() {
$(current['Textbox']).attr('value', $(this).text());
hideDiv();
});
SetDivVisibility(show);
}
function trim(str, chars) {
return ltrim(rtrim(str, chars), chars);
}
function ltrim(str, chars) {
chars = chars || "\\s";
return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
}
function rtrim(str, chars) {
chars = chars || "\\s";
return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
}
String.prototype.startsWith = function(str) {
return (this.match("^" + str) == str)
}
The code simply checks the cache for all words starting with the textbox value. To use the custom form, use:
<Custom:AjaxLookupOnce
runat="server"
id="AjaxLookupOnce2"
BackgroundColor="#EEE"
DivBorder="1px solid #CCC"
DivPadding="2px"
DivFont="Arial"
HighlightColor="green"
CallBackFunction="Index.GetRecords" />
Now, let's look at the server response for this:
As you can see, two keys have been typed and only one response has come back from the server on page load.
Now, let's compare it with a modified (enables multiple instances, use JQuery and onkeyup
rather than onkeypress
) version of firefalcon's article. This method calls the server each time a key is typed.
Two keys have been typed again, but this time it has two server responses taking 1 sec each, therefore at least a second's delay in the drop down.
Both versions and another simple example are available in the accompanying zip file.
Acknowledgement
Thanks to firefalcon for the original version which I have modified.