jQuery Auto-Complete Text Box with ASP.NET MVC

clip_image004This is an excerpt from Chapter 13 in my upcoming book, ASP.NET MVC in Action.

_________________________________________________

These days it is not uncommon to have text boxes automatically suggest items based on what we type. The results are further filtered as we type to give us the option to simply select an available item with the mouse or keyboard. One of the first examples of this in the wild was Google Suggest.

clip_image002

Figure 13.1 Google Suggest filters options as you type

A rudimentary implementation of this would simply monitor key-presses and fire off ajax requests for each one. Of course this means that fast typist would trigger many requests, most of which would be immediately discarded for the next request coming in 5 milliseconds. A good implementation will take into account a typing delay and also provide keyboard/mouse support for selecting the items.

Luckily jQuery has an extensive list of plugins available. One such plugin is Dylan Verheul’s autocomplete.

Dylan Verheul’s autocomplete

You can download the autocomplete plugin at http://www.dyve.net/jquery/ along with a few others including googlemaps and listify.

The basic idea is you have a simple text box on your page. The jQuery plugin adds the necessary behavior to handle key press events and fire the appropriate Ajax requests off to a URL that will handle the request. The URL needs point to a controller action, and by convention the response is formatted in a special way so the plugin could handle the response.

Assume for our purposes that we wanted to filter US Cities in the text box. The first step is to add a controller, action, and view for displaying the UI for this example. Ensure that jquery (in this case jquery-1.2.6.js) and jquery.autcomplete.js are referenced at the top of the view (or master page).

<script type="text/javascript" src="../../scripts/jquery-1.2.6.js"></script>
<script type="text/javascript" src="../../scripts/jquery.autocomplete.js"></script>

Next, add the text box. In this example we will call it city.

<%= Html.TextBox("city") %>

Package this up with a simple controller (Listing 13.1).

Listing 13.1 – a controller & action for displaying our test page

public class HomeController : Controller 
{ 
     public ActionResult Index() 
     { 
          return View(); 
     } 
} 

clip_image004

Figure 13.2 – Our simple view with a text box.

Now we add a little Javascript to add the autocomplete behavior.

<script type="text/javascript"> 

$(document).ready(function() { 
     $("input#city").autocomplete('<%= Url.Action("Find", "City") %>'); 
}); 

</script>

Place this in the <head> of the page. You can see that the URL for the autocomplete behavior is specified as Url.Action("Find", "City"). This will point to a Find() action on the CityController. We'll need to write this controller & action next.

Local Data Mode

The autocomplete plugin can also filter local data structures. This is useful when you have a limited set of data and you want to minimize requests sent to the server. The autcomplete plugin in local mode is also much faster, since there is no Ajax request happening behind the scenes. The only downside is that you must render the entire array onto the view.

Listing 13.3 – An action to find cities from an autocomplete ajax request

public class CityController : Controller 
{ 
  private readonly ICityRepository _repository; 
  
  public CityController() 
  { 
    //load up a CSV file with the city data
    string csvPath = Server.MapPath("~/App_Data/cities.csv");
    
    //the repository reads the csv file
    _repository = new CityRepository(csvPath); #2 
  } 

  //this constructor allows our tests to pass in a fake/mock instance
  public CityController(ICityRepository repository) #3 
  { 
    _repository = repository; 
  } 

  //the autocomplete request sends a parameter 'q' that contains the filter
  public ActionResult Find(string q) #4 
  { 
    string[] cities = _repository.FindCities(q); 
    
    //return raw text, one result on each line
    return Content(string.Join("\n", cities));
  } 
} 

The details of the CityRepository can be found in the code samples provided with the book. For now, we will focus on the new Find(string q) action. Since this is a standard action, you can actually just navigate to it in your browser and test it out. Figure 13.3 shows a quick test.

clip_image006

Listing 13.3 – A simple HTTP GET for the action with a filter of "hou" yields the expected results.

Now that we are sure that the action is returning the correct results, we can test the textbox. The Javascript we added earlier hooks up to the keypress events on the textbox and should issue queries to the server. Figure 13.4 shows this in action.

clip_image008

Figure 13.4 – The results are display in a <ul> tag. We can apply CSS to make it look nicer.

The drop down selections are unformatted by default, which makes them a little ugly. A little CSS magic will make it look much nicer. Listing 13.4 shows some sample CSS for this.

Listing 13.4 – CSS used to style the autocomplete results

<style type="text/css"> 

div.ac_results ul { 
  margin:0; 
  padding:0; 
  list-style-type:none; 
  border: solid 1px #ccc; 
} 

div.ac_results ul li { 
  font-family: Arial, Verdana, Sans-Serif; 
  font-size: 12px; 
  margin: 1px; 
  padding: 3px; 
  cursor: pointer; 
} 

div.ac_results ul li.ac_over { 
  background-color: #acf; 
} 

</style> 

clip_image010

Figure 13.5 – The styled dropdown results look much nicer. The selected item is highlighted, and can be chosen with the keyboard or the mouse.

The auto-complete plug-in has many options for you to configure to your needs. For the simple case that we've shown here, it's as simple as this:

$(your_textbox).autocomplete('your/url/here'); 

Other options for the plugin are listed below:

inputClass This class will be added to the input box.
resultsClass default value: "ac_results"
loadingClass The class to apply to the input box while results are being fetched from the server. Default is “ac_loading.”
lineSeparator Default is \n
minChars The minimum # of characters before sending a request to the server. Default is 1.
delay The delay after typing when the request will be sent. Default is 400ms.

 

There are many more options, but these are some common ones. To set these options, you include them in a dictionary as the second argument to the autocomplete method like this:

$("input#city").autocomplete('<%= Url.Action("Find", "City") %>', { 
     minChars : 3, 
     delay : 300 
}); 

This type of functionality is immensely useful for selecting from large lists. It keeps your initial page size down by not loading all of these items at once and is very user-friendly.

________________________________________

clip_image004This is an excerpt from Chapter 13 in my upcoming book, ASP.NET MVC in Action.

#1 EricTN avatar
EricTN
4.21.2009
12:50 PM

This was a great article! Thanks.


#2 Karthik avatar
Karthik
4.21.2009
1:18 PM

Love the example, but is there a way to not deal with the "Find", "City" magic strings in the JS autocomplete() call?


#3 benscheirman avatar
benscheirman
4.21.2009
1:48 PM

@Karthik: of course! using MvcContrib, you could do this:

<%= Url.Action<CityController>(x=>x.Find("")) %>


#4 masster avatar
masster
4.21.2009
3:03 PM

Realy nice and very helpful post. May I translate it in russian in my blog <a href="http://compupro.ru">compupro.ru</a> with backlink to this post


#5 benscheirman avatar
benscheirman
4.21.2009
3:38 PM

@masster - Be my guest:)


#6 Angeline  avatar
Angeline
4.24.2009
11:11 PM

Can u pls give an example of autocompleter with key value pair feature?


#7 DavidLGoldberg avatar
DavidLGoldberg
4.26.2009
2:53 PM

Great article thanks!


#8 Rama avatar
Rama
5.13.2009
4:15 AM

Nice Article!

I want to send the another parameter of dropdownbox selected item to .aspx page.

I have set link as "testauto.aspx?test='"+document.getElementId("ddlcities").value+"'")

I am able to send the value of dropdown box, but same value of dropdown box is coming when I change the selected value.

Please let me know how to solve this


#9 Steve avatar
Steve
5.30.2009
10:48 PM

I also need a key - typically I'm showing text, but needing to get the id so I can, ie. execute some code once the item is select by knowing the key.

(This is where I've had problems with this particular jquery plugin)


#10 KevinUK avatar
KevinUK
6.01.2009
6:55 AM

I'm also wondering how to return key/value pairs. Once I have that information, how can you redirect them to a view with the selected value?


#11 trendbender avatar
trendbender
6.01.2009
7:20 AM

good example)


#12 Alex avatar
Alex
6.02.2009
7:52 AM

Unfortunately this works only on FF, but not on IE8 (with and without compatibility mode). Any ideas :\ ?


#13 güvercin avatar
güvercin
6.06.2009
2:18 AM

very nice example.


#14 How to play casino craps games avatar
How to play casino craps games
6.23.2009
6:07 AM

I'd be on the same side with Karl: I would avoid mixing server-side code with JavaScript (jQuery) as soon as it is convenient enough. We do need server-side code if some data should be passed from server. To me, a habit of generating client code by server code already made MS Ajax overcomplicated. Let's not ruin jQuery too.


#15 LUPISS avatar
LUPISS
7.28.2009
10:07 AM

Hello. I have tried to make the example we get but I have not had success. I would like to know how to use the structure of MVC, and bring data from a storeprocedure.

Thanks


#16 benscheirman avatar
benscheirman
7.28.2009
10:09 AM

@LUPISS - Where the data comes from is not really important. What is important is that you format it the way the plugin wants, in this case each element on a new line.

Put this code in a controller and test it out by navigating to the URL as I have done in this post.


#17 Lupiss avatar
Lupiss
7.28.2009
1:56 PM

I think I will study more because I can´t with the autocomplete .. :'(

One question in this book your is in Spanish ?


#18 benscheirman avatar
benscheirman
7.28.2009
2:05 PM

@Lupiss - The book is in English.


#19 Chocolim avatar
Chocolim
7.31.2009
2:29 PM

Love the this is what you need to do style. Just pre order it in amazon


#20 utahgolfer avatar
utahgolfer
8.04.2009
4:31 PM

Was just reading your posts and was wondering if you know how to set a style to the substring of found items? For example if start typing and i want whichever letters i type to appear bold, how can i accomplish that?

Thanks for any help.


#21 benscheirman avatar
benscheirman
8.04.2009
4:34 PM

@utahgolfer - not sure what you're asking: browser find? (CTRL+F)? or are you looking for a jquery plugin?


#22 Chris avatar
Chris
8.14.2009
5:28 AM

Hi, great article... but how easy would it be to change from a CSV file to a recordset generated by a SQL query?

Thanks in advance


#23 hasfa avatar
hasfa
8.19.2009
10:15 AM

Great article, good example and nice tutorial.

Thanks Guys.


#24 Kyle avatar
Kyle
8.24.2009
2:21 AM

Hello, I need this to work with ASP/SQL 2008 DB.

But i'm a bit of a noob...

please advise


#25 Balaji avatar
Balaji
8.26.2009
5:50 AM

Nice article. I have a problem while using jquery. For each text field in my page i should have a call to the jQuery call with that object id.

Is there a way to have a common call to all the textfields?


#26 Pragnesh avatar
Pragnesh
9.10.2009
4:53 AM

hi ben,

thanks for sharing good article.

Please could you post sample code to download?

Thanks


#27 Jeremy Durnell avatar
Jeremy Durnell
10.01.2009
3:54 PM

Just an FYI...

I was having trouble getting the results to render properly in IE 7 & 8. I solved the problem by commenting out lines 281-284 in jquery.autocomplete.js.

It looks like the code uses an iframe in IE to provide a backdrop for the results so that the other HTML elements don't bleed through. This frame was rendering really ugly in IE 7 & 8. My work around is to use a background color for the ac_results class.


#28 Jakob Olsen avatar
Jakob Olsen
10.13.2009
1:55 PM

Hi.

I can confirm Jeremy's observation. Commenting out lines 281-284, and adding background color to the dropdown solves the rendering problem in IE.

Otherwise... Thanks for a brilliant walkthrough.