Sep 1, 2005 / Implementing Ajax.NET-based Lookup Server Control

17 comments

Source code can be downloaded from http://www.targetprocess.com/download/AjaxLookupControlSources.zip

AJAX is one of the most popular buzz words today. Idea is not new, but for some reasons it became popular during last half a year. With web-applications popularity growth, users demand richer and faster interface. And AJAX could improve user experience.

Let me show one example of real user requirements. One of our customers need a way to quickly select user stories and bugs. For example, bugs could be linked to user story and there is a drop down for that purpose. But when you have about 100 items in drop down it is just unusable and lookup control makes selection simpler. That could be achieved via lookup control (like Google Suggest), so we decided to use AJAX in our product to implement this features, improve user interface and make it more responsive. AJAX is completely new for me. I read some general articles about idea sometime ago, but didn't try anything due to lack of tools and ready to use libraries. However, recently I found Ajax.NET – quite powerful framework that supports asynchronous callbacks. Examples are simple and I decided to use it to reach the goal.

In this article I'll describe my experience of creating lookup control based on Ajax.NET. To build lookup control you need quite a few things:

  1. Server method that will return a list of matched records
  2. JavaScript that will handle post-backs and show a list of matched records
  3. Input field on aspx/ascx page.

I will not describe Ajax.NET installation, since it is very simple and there are some great sources that you should check.

Server Side Part

This part was really simple. All I had to implement is one simple method that returns ArrayList of matched records and register class where this method located:

public class Main : Page
{

private void Page_Load(object sender, EventArgs e)
{
 Utility.RegisterTypeForAjax(typeof (Main));
}

[AjaxMethod()]
public ArrayList GetSearchItems(string query)
{
 // use real method ti query data from database instead
 ArrayList items = GetRecords();

 ArrayList matchItems = new ArrayList();

 foreach (string item in items)
 {
  if (item.ToLower().StartsWith(query.ToLower()))
   matchItems.Add(item);
 }
 return matchItems;
}

private ArrayList GetRecords()
{
 ArrayList items = new ArrayList();
 items.Add("Ted");
 items.Add("Teddy");
 items.Add("Mark");
 items.Add("Alfred");
 return items;
}
. . .

GetSearchItems method gets a list of all records from any source and filter records that start with query parameter. Query is what user types in input field.

Client Side Part

Firstly, I decided to write very simple JavaScript that will show DIV with found records right under the query input field. "One step closer", I thought. But it is required to select one of the items below. The simplest thing is turn all items to hyperlinks and fill query field with correct value on click. Here what I’ve got:

<INPUT id=search type=text name=search runat="server"
autocomplete ="off">
<div id="list"></div>

autocomplete=”off” is required to tell browser to not show possible values for input field. Otherwise our control will not work.

function GetSearchItems_CallBack(response) {
var div = document.getElementById("list");
div.innerHTML = "";
if (response.value != null && response.value.length > 0) {
 for (var i = 0; i < response.value.length; ++i){
 div.innerHTML += "<a href=\"javascript:Fill('" + response.value[i] + "');\">"
+ response.value[i] + "</a><br />";
}
}

JavaScript GetSearchItems_CallBack function should be bended to onkeydown event. This could be done in code behind or right on *.aspx page. Let’s use code behind.

private void Page_Load(object sender, EventArgs e)
{
search.Attributes.Add("onkeydown",
"javascript:Main.GetSearchItems(this.value,GetSearchItems_CallBack);");
Utility.RegisterTypeForAjax(typeof ( Main ));
}

The result looks like that:

While it is simplest thing, it is not very usable. You type something, then take a mouse and click a link in appeared list – too many actions. What is required is a cool keyboard support. People should be able to use up/down keys for list navigation and enter key for completion.

Looking for JavaScript

I never handled keys in javascript before and too lazy to write quite large and complex script by myself. I know JavaScript, but not as good as C#, so my first reaction was "Let's find something ready to use and adopt it for my needs". I should say that there are not so many free scripts available. I spent about an hour to find a good one. So can't miss a reference. Thanks Julian Robichaux for the really fancy script with great comments (It is quite rare for free scripts, and for commercial as well :)

The script provides a function that query server, but I need a custom one. Luckily, the only change required is in mainLoop function.

mainLoop = function() {

 val = escape(queryField.value);
    
 if(lastVal != val && searching == false){
    
  var response = Main.GetSearchItems(val);
  showQueryDiv('smi', response.value); lastVal = val;
 }
    
 setTimeout('mainLoop()', 100);
 return true;
};

Script shoul be enabled via onload handler:

<body onload="InitQueryCode('search')">

Finally, I've got what I want within quite small amount of time. But the solution was not reusable, so I decided to create simple server control.

Server Control

Ajax Lookup server control is a really simple thing. The following parts of existing solution should be customizable:

  • Name of callback function
  • Path to javascript file
  • Colors like matched list background and highlight, div padding and so on

It is possible to invent something else, but I didn't require more and, in general, I don't like add complexity if it is not required in nearest future.

Implementation is fairly simple. We may inherit our control from TextBox. Then all we need to do is set some variables and register some javascript functions.

using System;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace play
{
/// <summary>
/// AjaxLookup.cs
/// </summary>
public class AjaxLookup : TextBox
{
 private string scriptFile = "";
 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 ScriptFile
 {
  get { return scriptFile; }
  set { scriptFile = value; }
 }

 public string CallBackFunction
 {
  get { return callBackFunction; }
  set { callBackFunction = value; }
 }

 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; }
 }

 public AjaxLookup()
 {
  this.Attributes.Add("autocomplete", "off");
 }

 protected override void Render(HtmlTextWriter writer)
 {
  base.Render(writer);

  // bind script that contains almost all logic
  Page.RegisterStartupScript("LoadScript", 
"<script language='JavaScript' src='" + ScriptFile + "'></script>");

  // include UI settings
  string styles = String.Format(
   @"<script language='JavaScript'>
    var DIV_BG_COLOR = '{0}';
    var DIV_HIGHLIGHT_COLOR = '{1}';
    var DIV_FONT = '{2}';
    var DIV_PADDING = '{3}';
    var DIV_BORDER = '{4}';
   </script>",
   BackgroundColor, HighlightColor, DivFont, DivPadding, DivBorder);

  Page.RegisterStartupScript("LookupStyles", styles);

  // initialize postback handling
  Page.RegisterStartupScript(
   "RegisterScript",
   "<script language='JavaScript'>InitQueryCode('" + this.ClientID + "')</script>");

  // set correct calllback function
  Page.RegisterStartupScript("RegisterCallBack", @"<script language='JavaScript'>
  
   mainLoop = function() {

    val = escape(queryField.value);
    
    if(lastVal != val && searching == false){
    
     var response = " + CallBackFunction + @"(val);
     showQueryDiv('smi', response.value); lastVal = val;
    }
    
    setTimeout('mainLoop()', 100);
    return true;};
   </script>");
 }
}

Control can be used this way:

<Ajax:AjaxLookup
 Runat="Server"
 id="search"
 BackgroundColor="#EEE"
 DivBorder="1px solid #CCC"
 DivPadding="2px"
 DivFont="Arial"
 HighlightColor="#C30"
 CallBackFunction="Main.GetSearchItems"
 ScriptFile="lookup.js" />

And here like Lookup Control looks in production:

This implementation is not ideal, but quite good to start from. You may improve flexibility, add some additional parameters and so on.

Conclusion

In general, Ajax.NET could be helpful for many useful things. It is reasonable to start from the simplest things like lookup, but I hope in future many parts of UI in our project will be based on Ajax.

17 Comments:

At 9:36 AM, Anonymous Anonymous said...

Very good article about creating controls with Ajax.NET!

 
At 5:46 PM, Anonymous Anonymous said...

Have no idea how to send you a track, so here's the link
Implementing MonoRail-based Autocompletion

 
At 2:55 AM, Anonymous Anonymous said...

cfg

 
At 5:46 AM, Blogger Michael Dubakov said...

MonoRail implementation looks simpler if it was not required to implement InputTextWithAutoCompletion helper.

 
At 8:42 AM, Anonymous Anonymous said...

I am getting a:
'Main' is undefined
error when i try and use this. I assume it is related to the CallBackFunction, but I dont know how to fix it.

 
At 11:00 AM, Blogger Michael Dubakov said...

Main is a name of code behind class.

It should be the same at:

1.
public class Main : Page
{

2.
private void Page_Load(object sender, EventArgs e)
{
Utility.RegisterTypeForAjax(typeof (Main));
}

3.
<Ajax:AjaxLookup
...
CallBackFunction="Main.GetSearchItems"
...

Hope this will help

 
At 11:34 PM, Anonymous Anonymous said...

Would appreciate any assistance with getting this working with multiple fields on the one form. My javascript skills are a bit untuned, I'm guessing I have to mod the functions to all pass the queryField instead of having a global query field. Has anyone already done this?

THanks,
J

 
At 4:27 PM, Anonymous Anonymous said...

Very useful article, thank you!

 
At 6:11 AM, Anonymous Anonymous said...

After some digger around and working out that it is nothing to do with masterpages, but folders!

So it turns out to be looking in the worng path!

So this made me look at the lookup.js file and then I noticed my mistake!!

in the Ajax control make sure that the ScriptFile point to the correct location :))

All working.

This is a great article for Ajax and ASP.Net controls.

Thanks,
Nick

 
At 6:10 PM, Anonymous Anonymous said...

There is a new demo for Ajax.NET controls:

http://groups.google.com/group/ajaxpro/browse_thread/thread/1bf05520823f9646/b397373d15fd3fd2

 
At 8:48 AM, Anonymous Anonymous said...

Thank you for your Ajax-lookup-component. It works fine!
But why can't I use 2 Ajax-lookup-controls on one page. Only one of them is working.

Rieni

 
At 3:36 AM, Blogger Michael Dubakov said...

I will try to check this constraint over the weekend. Sorry, I am really under time preassure now, so don't have a chance to do it earlier.

 
At 8:58 AM, Anonymous Anonymous said...

I've tried to add a scroll bar to the block of search result. It works on Firefox, but doesn't work on IE. The problem is that when I click on the scroll bar, the result is gone (div goes away). Any ideas?

 
At 8:59 AM, Anonymous Anonymous said...

I've tried to add a scroll bar to the block of search result. It works on Firefox, but doesn't work on IE. The problem is that when I click on the scroll bar, the result is gone (div goes away). Any ideas?

 
At 12:50 AM, Anonymous Anonymous said...

Can you please look into the issue that it is not working when 2 controls are put on page

 
At 3:15 AM, Anonymous Anonymous said...

I cannot add 2 controls on the page as one is working and one is not. Can you lps tell me the way out

 
At 7:53 AM, Blogger Unknown said...

Great.. Do you think you can help take a look at our 100% AJAX social network site? Let me know if you think we have implemented AJAX properly here. You can test out the sign up form, drag and drop the modules and also commenting on a video (we have used AJAX everywhere :) Thanks.

Site: http://www.ecpod.com

 

Post a Comment

<< Home

Subscribe to the RSS feed
Stay tuned by having the latest updates via RSS
Follow TargetProcess on Twitter
Get in touch with our team

TargetProcess is an agile project management tool. It is designed to solve distributed teams problems and support agile development processes.



Key Features

  • Full Agile Project Management Support (Scrum, XP, Custom)
  • Productivity Tools (Tp.Tray, ToDo list, dashboards, Inline editing)
  • Customizable Development Process
  • Subversion Integration
  • Integrated Bug Tracking, Help Desk, Time Tracking

Get TargetProcess for Free (5 users pack)

Previous Posts



    follow me on Twitter