This blog has, IMO, some great resources. Unfortunately, some of those resources are becoming less relevant. I'm still blogging, learning tech and helping others...please find me at my new home on http://www.jameschambers.com/.

Wednesday, March 31, 2010

jQuery AutoComplete with ASP.NET MVC

At the time of this post, I am using Visual Studio 2010 Ultimate RC1.

I recently posted an entry on using a common jQuery plugin that added auto-complete functionality to a textbox. The jQuery UI team has just added a component to the library that provided a more streamlined implementation, closer to the feel of other components and with similar syntax for initiation, events and methods.

The widget actually appears to be authored by the same programmer, only with a much stricter focus on adhering to the jQuery style.

This is a great move and the component has really upped the ante for this release as it’s moved to the core jQuery UI library.

Scroll to near the end to get a point-form walk through. The very end of my post includes a project download.

Revamp or Rewrite?

There will be a few people who are locked into a particular build of a component, who inherit some code or are otherwise just interested in an older version for whatever reason. There are also some important changes to get the new autocomplete to work, as well as some things we no longer need to do. Some of those tricks may be relevant to other plugins, so I’m leaving my original post intact.

My last post was also just a list of things you’d need to do differently from a separate walk-through, so I’m taking this opportunity to write a complete run.

Prerequisites

If you want to follow along with this post you’ll need the following goodies:

  • Visual Studio 2010
  • The jQuery UI library downloaded with the jQuery library (and any theme you like) rolled in
  • I usually also grab the latest VSDOC file so that I get intellisense in the IDE.

Basic Setup Tasks

Finally. Some meat! We’re going to do the following:

  • Create an ASP.NET MVC project
  • Configure the project (overcome a minor template bug)
  • Add the JS and theme files to the project
  • Create a master page and setup the includes needed

Okay, let’s go. Crack open Visual Studio and navigate to the new project dialog. To filter the list of project types down, select ‘Web’ and then click on ASP.NET MVC 2 Empty Web Application.

Because of an omission in the template for this project type you’ll need to add two lines in the web.config yourself. This is the web.config located in the root of the project. Make sure your assemblies section contains the lines located here.

Next, we need to get jQuery up to speed (the autocomplete widget requires a newer library), and make our site aware of it. In the Solution Explorer, open up the scripts folder and delete the three 1.3.2 files for jQuery.

image

Drill into your jQuery UI theme download and get the files from the JS directory. Add those to the script folder in your project. Add the VSDOC file as well, if you’re using it.

imageWe also have to get the theme implemented (if you are so inclinded). Right-click on your project and add a new folder called Content. Copy the themes directory from your jQuery download and paste it in the Content folder your just created.

Now we’ll get our project started by adding a master page to the mix. On your Views –> Shared project folder, right-click and add a new item. Select MVC 2 View Master Page from the list and call it Site.Master because all the cool cats do. In the head tag of the page add the scripts we need to light up the plugin.

image

If you’re using a theme, you’ll also want to include the required style sheets to make everything appear correctly as I have above. You can easily add the above references by dragging them from Solution Explorer to your head section.

A Simple Start

Let’s test at this point to make sure we’re setup correctly. In this section we’re going to do the following:

  • Add a Home controller
  • Add a default view (Index) to the project
  • Create a div that will be styled to reflect a properly configured style sheet
  • Add a small block of code to ensure jQuery is rigged up correctly

Right-click on the Controllers project folder and select Add –> Controller. I’ve used HomeController as the name as it’s the convention used by most.

image A very simple class is created for you that represents the first controller. At this point, right-click on the method name “Index” and select “Add View…” from the context menu. The default options work for me (Index as the name, not a partial view, not strongly typed, use the master page I just created).

Side note: because the default route is set up for us in Global.asax, our Home controller with the Index action method will be used for requests to the default document on our site.

To make sure our site is rigged with jQuery and the proper styling, add the following markup to the page:

<div id="test-panel" class="ui-state-default">
This panel will disappear on command.</div>

<script type="text/javascript" language="javascript">
$(function () {
$('#test-panel').click(function(){
$(this).slideUp();
});
});
</script>

We should be good to go for our test! Run the app (F5). You may be prompted to modify the web config…do that and watch the page come up. Clicking on the div shows that jQuery is lit up and we’re good to go. Your page should look similar to this:

image

If clicking the panel doesn’t make it slide up or you do not have the shading on the div you should go back and make sure you’ve not missed any steps.

Lighting up Autocomplete

I want to demonstrate the basic functionality of the widget, but I also want to ensure that we’re following some of the practices commonly used in MVC projects. Here’s where we’re going to start:

  • Clean up our index view
  • Add a textbox to the view
  • Add a model class and a corresponding repository that returns a fake set of data
  • Create a PartialViewResult method that can be used to retrieve the data
  • Add the script needed to call our controller’s action and populate the autocomplete box

Start by quickly removing any of the test code we created, but leave the script block in place as we can reuse that. All that should remain inside your content placeholder is the page title and said block. Add an input with an ID of “name-list” to the page and you should be setup like so:

image

In your Solution Explorer, right-click on Models and add an new class called Person. Add two properties, an int for the PersonId and a string for FullName. This class took about 4 seconds to write as I used the prop code snippet (type “prop” then tab-tab). It’s not a terribly complicated class to begin with, but it’s a great shortcut!

image

Add another class to the Model folder called PersonRepository. Create an internal method in it of type List<Person> called FindPeople. The method should accept a string for the search text and an int for the max number of rows to return.

image

This is just a simple method with a LINQ query to filter the list. We also cap the number of results with the Take() method.

I have omitted some code above; what I have there is a whole bunch of random names added to the names list. The code is in the project download and I got the random names from this site.

With our model and repository in place, we’ll next create the method to be called by the autocomplete plugin to get our list of results.

Side note: We have to be aware that binding will occur to the parameters of the post automatically for us and that reflection is used to map the variables. This is case sensitive and, at the current time, there is no tooling support to make sure we get this right. Your request will just fail in transit and you won’t see anything in the browser. If you’re experiencing this, I highly recommend downloaded FireFox and FireBug to capture and view the requests/responses at play.

I find the following interesting points to mention of the following code:

  • We are using an HttpPost to help prevent scripting attacks or data exposure from direct JSON queries
  • We use a JsonResult as the return type on the method
  • Json() is used to convert our List<Person> – the response type from the repository’s FindPeople method – to the proper JSON notation
  • The default behaviour of Json() does not allow GET (but you can override this if required)

image

And finally, we’re going to write a short burst of JavaScript to complete the mix. At this point we’re only specifying the source but here are some observations:

  • We use a function as the ‘source’ property to post data to our controller action
  • The function uses an ajax call with the path to the action
  • We specify that we’re using POST and that the data is formatted as JSON data
  • We define a success method to map the data of the response back to an item array of our format
    • label and value properties are used by plugin
    • we can store additional data in the result (for example, the id) for later use

The contents of the script block should be as follows:

image

That’s it?

Yeup. That’s it. Press F5 to run the application and you’ll be able to start auto-completing some things that you type.

Here’s an overview of the steps required, without the bits for the setup testing:

  • Create the VS2010 ASP.NET MVC 2 empty project
  • Correct web.config’s assembly references
  • Add the appropriate jQuery and jQuery UI libraries to the project
  • Create a master page and add the script and CSS references
  • Create a Home controller with an Index action
  • Implement a repository that accepts search criteria and returns model data
  • Write a JsonResult method with an HttpPost attribute which accepts search parameters and calls the repository
  • Use jQuery to rig-up a text input with the call to the controller action

But what if you want to do something interesting with the result of the user’s selection?

Good question, and it’s what got me started on this whole thing.

The Fun Bits

If you are working with a data-driven site (and likely you are, if you’re in any modern web programming environment) you know that IDs are king.

Luckily we are able to store as complex an object as required in the JSON result. The PersonId from my Person model was stored in the UI element, just as was the display name.

Here is the success function extracted to show the ID getting stored:

image

So, how do we get it out? Autocomplete actually keeps track of the array for us and allows us to access the selected item when the select event fires. We define a function for this event and pass it in as one of the options for Autocomplete with a couple of parameters. We can then access any properties we attached to the array when we parsed it above:

image

Now, simply access the ui.item object and any of the properties you setup in the success parser will be available. You can do much more interesting things than just alert(), obviously, but this demonstrates how easy it is to use the plugin with dynamic data where IDs are required.

Conclusion

The addition of Autocomplete as a widget to jQuery UI is a welcome and surprisingly mature component. You will find that with a few setup points and relatively painless coding you will be able to add AJAX-enabled autocomplete functionality to a textbox. It’s also clear that ASP.NET MVC is well suited to integrate with this type of behaviour.

Download the project file here.

30 comments:

  1. You are awesome. I love the fact that you always provide the sample code. I have learnt a great deal of knowledge from your blog. Keep up with the good work !

    -Antony

    ReplyDelete
  2. lol...thanks Antony. Glad to know it's helping someone out. Cheers!

    ReplyDelete
  3. That was awesomely helpful when I was in a clutch position. Autocomplete was being particularly problematic for me when I was used to the relative ease of jQuery.

    ReplyDelete
  4. finally, an article that actually works...

    ReplyDelete
  5. Tried a number of other "solutions", but only yours worked. You rock dude.

    ReplyDelete
  6. Thank you, it works!

    ReplyDelete
  7. Like others before me I tried a few different solutions and couldn' get it working. I am from a .net background and had no issues using this solution at all. thank you!

    ReplyDelete
  8. Really useful. Thanks.

    ReplyDelete
  9. Awesome . Spent hours on this searching and your solution worked in less than a minute.

    ReplyDelete
  10. Really great example...just had to make a slight change to enhance this for my purposes and that was to make the search term case-insensitive.

    ReplyDelete
  11. hey james,
    thanks for giving us this awesome example which helped me a lot.unfortunately i have another issue.i am developing a site which has single index page for diff url's.
    for example www.abc.com.au/melbounre,
    www.abc.com.au/sydney,www.abc.com.au/perth...etc

    this index page has search option so users can search items in state wise.so i need to show filtered suburbs according to the states.which means i need to pass current state into database and then grab relevant suburbs

    this is my code for javascript...

    $('#suburb').autocomplete({
    source:function(request,response){
    $.ajax({
    url: "http://localhost:61025/Search/GetSuburb", type: "POST", dataType: "json",
    data:{ searchText:request.term,maxResults:10,currentState:state},
    success: function(data){
    response($.map(data, function(item){
    return { label: item.SuburbName +'-'+item.Postcode , value: item.SuburbName , id: item.SuburbName }
    }))
    }
    })
    }
    });

    i just tried to pass <%= Model.CurrentState %> but no luck.could you please give me a solution for this.

    ReplyDelete
  12. Hey anon...a couple things to suggest and help you along.

    1) Don't static-code your url in the ajax call. use <%: Url.Action("") %> to generate this. that will help with any changes to ports and later your deployment

    2) Attach Firebug. Get it (it's for firefox) if you don't have it. It will show you what's being returned, most importantly Ajax errors and behind-the-scenes crap that isn't exposed when something in the ajax world fails.

    3) Set a breakpoint in the controller action and see what data's coming in. Make sure your searches are case-insensitive.

    Beyond that, when you say 'no luck' are you getting errors? Can you post it somewhere for me to take a look at?

    ReplyDelete
  13. Hi

    I have a strange issue with auto-complete plugin. I am doing the same as your example and using auto-complete to send an ajax request to get the list and when user select an item in the list, it will change the value of a hidden textbox with the key of the item and displays the test of the item in a visible textbox. Pretty much same as a drop down list but because I have a long list I avoid using the drop down list.
    The problem I have is in some of the machine (with IE7 or IE8) when user selects the item, the select event doesn't trigger.
    I am using auto-complete plugin from jQuery 1.4.2

    Your help would be highly appreciated.

    Regards

    Behnam

    ReplyDelete
  14. Behnam,

    Were you able to get the sample I provided working? I just want to rule out any chance of it being a config issue on your machine.

    Let me know!

    Cheers.

    ReplyDelete
  15. Behnam,

    Thanks man that helped. Im just getting into JavaScript and this saved a lot of hair loss!

    Regards,

    Ian

    ReplyDelete
  16. Thanks for this writeup, been struggling with autocomplete with asp.net mvc for a week off and on, this did the trick!!!

    ReplyDelete
  17. Very helpful. Thank you.

    ReplyDelete
  18. Thx a lot for this.

    ReplyDelete
  19. Hi,
    thank you very much for this fantastic workaround!

    But I have a problem with the Javascript, I did every thing excactly like in the manual but with my own model.
    Ervery thing ist running, debuging the C# code shows me that the controler is returning the correct data, but debuging the javascript with firebug only shows that there are no data in the datafield.

    Debuging your solution shows that the javascript is running 2 times (means first time the datafield is also empty, 2. time the correct data are inside)

    Do you know anything about this problem and how to solve it?
    Greeting and many thanks!

    ReplyDelete
  20. You are excellent tutor.
    Thank u very much

    ReplyDelete
  21. Great job Mr James!
    Thanks

    ReplyDelete
  22. Please use source code, not images for your code samples.

    ReplyDelete
  23. Hi and thanks for the tutorial. I've implemented this into something I'm working on but have one outstanding issue. I'm using MVC3 and binding a model to my view. The ID for the autocomplete is stored in a hidden field and my model is bound to this field. I set the hidden field where you were showing the alert message with the select: function(event,ui). If the user clears the textbox or enters invalid data into the textbox, I need to set my hidden field to 0 or null. Any suggestions?
    Thanks,
    Jeff

    ReplyDelete
  24. @Jeff You can use jQuery to test for this, along the lines of this SO question:
    http://stackoverflow.com/questions/805963/how-do-i-bind-onchange-event-of-a-textbox-using-jquery

    You'll need to listen for either the textbox onchange event (fires when textbox validates) or as in that sample, with keydown. Hope that helps.

    @FreshCode: This is something I struggle with. Code formatting is a PITA and I don't have a good plug in for it (I'm on Blogger here). That is why I provide the full download of the working project at the end of the article. Sorry for the trouble.

    Cheers.

    ReplyDelete
  25. Great post, works and is concise.

    ReplyDelete
  26. This solution worked for me after I stringified "data" using JSON.stringify() in ajax call. Before that "searchText" wasn't passed to Action Method 'FindNames'. Maybe currently someone else has the same problem so i'm writing hoping i can help :)
    And of course - James thank you for a great post!

    ReplyDelete
  27. Great Article with clean code.. thanks

    ReplyDelete