Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

AJAX Look Up Caching

4.50/5 (4 votes)
22 Feb 2009CPOL2 min read 21.6K   174  
Cache server response to reduce server load.

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:

C#
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)
        {
            //Set search to call the simple example
            this.search.Attributes.Add("onkeyup", 
              "Index.GetSearchItems(this.value, GetSearchItems_CallBack);");
            Utility.RegisterTypeForAjax(typeof(Index));

            //Add autocomplete off using javascript to keep valid XHTML 1.1
            Page.ClientScript.RegisterStartupScript(typeof(System.Web.UI.Page), 
              "SeachAutocomplete", "<script type=\"text/" + 
              "javaScript\">$('#search').attr('autocomplete', 'off')</script>");
        }

        [AjaxMethod()]
        public ArrayList GetSearchItems(string query)
        {
            // use real method to query data from database instead

            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:

C#
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;

            //Set the onkeyup attribute to call the function.
            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):

JavaScript
var current;
var cache = new Array();

//Create result div for the textbox and cache it in the array cache
function InitializeTextboxEvent(TextboxName, StyleArray)
{
    //add "autocomplete=off" and blur attributes to the textbox
    $('#' + TextboxName).blur(hideDiv).attr('autocomplete', 'off');

    //Cache the textbox id, result div id, 
    //result div style array and result array
    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];
}

//Create result div
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:

C#
protected override void Render(HtmlTextWriter writer)
{
    if (callBackFunction != string.Empty)
    {
        base.Render(writer);

        //Set the drop down box style and Initialize the textbox
        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:

JavaScript
function CacheArray(response)
{
    //Calls seperate function as 'this' variables are undefined in this function
    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.

JavaScript
//Check Value with cached results
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);
}

//Return cache for the textbox queryElement
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;
}

//Hide/Show result div
function SetDivVisibility(show)
{
    if (getCurrent())
    {
        if (show)
            $(current['ResultDiv']).css({ 'visibility': 'visible' });
        else
            $(current['ResultDiv']).css({ 'visibility': 'hidden' });
    }
}
function hideDiv()
{
    SetDivVisibility(false);
}

//Add hover and click events to the results
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);
}

//Trim and starts with
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:

ASP.NET
<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:

lightServer.jpg

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.

heavyServer.jpg

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)