50

I searched all over this site and the web for a good and simple example of autocomplete using jQuery and ASP.NET. I wanted to expose the data used by autocomplete with a webservice (and will probably do that next). In the meantime, I got this working, but it seems a little hacky...

In my page I have a text box:

<input id="txtSearch" type="text" />

I am using jQuery autocomplete, set up per their example:

<link rel="stylesheet" href="js/jquery.autocomplete.css" type="text/css" />
<script type="text/javascript" src="js/jquery.bgiframe.js"></script>
<script type="text/javascript" src="js/jquery.dimensions.pack.js"></script>
<script type="text/javascript" src="js/jquery.autocomplete.js"></script>

Here is where it starts to get hacky... I call a page instead of a webservice:

  <script type="text/javascript">
    $(document).ready(function(){
        $("#txtSearch").autocomplete('autocompletetagdata.aspx');
    });      
  </script>

In the page I stripped out ALL of the html and just have this (otherwise, various HTML bits show up in the autocomplete dropdown):

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="autocompletetagdata.aspx.cs" Inherits="autocompletetagdata" %>

And in my autocompletetagdata.aspx, I am using SubSonic to query, format and return data from the database (one data item per line):

protected void Page_Load(object sender, EventArgs e)
{
    // Note the query strings passed by jquery autocomplete:
    //QueryString: {q=a&limit=150&timestamp=1227198175320}

    LookupTagCollection tags = Select.AllColumnsFrom<LookupTag>()
        .Top(Request.QueryString["limit"])
        .Where(LookupTag.Columns.TagDescription).Like(Request.QueryString["q"] + "%")
        .OrderAsc(LookupTag.Columns.TagDescription)
        .ExecuteAsCollection<LookupTagCollection>();

    StringBuilder sb = new StringBuilder();

    foreach (LookupTag tag in tags)
    {
        sb.Append(tag.TagDescription).Append("\n");
    }

    Response.Write(sb.ToString());
}

If you don't do a LIKE query, then it returns everything that contains a match for the character(s) you type -- e.g., typing "a" will include "Ask" and "Answer" as well as "March" and "Mega." I just wanted it to do a starts with match.

Anyway, it works and it's pretty easy to set up, but is there a better way?

djuth
  • 985
  • 1
  • 8
  • 9
  • Thanks djuth, this works great and is very simple! I have to admit it is pretty limiting though, for example, I would love to have it autocomplete a staff name and then return a staffID but I can probably play around with what's added and parsed. – Mark Kadlec Jul 14 '09 at 23:27
  • 1
    This has now been deprecated and integrated with JQuery UI - http://www.learningjquery.com/2010/06/autocomplete-migration-guide shows how to migrate, not a lot to it! – Duncan Jul 23 '11 at 11:47

4 Answers4

32

I just recently implemented autocomplete, and it looks fairly similar. I'm using an ashx (Generic Handler) instead of the aspx, but it's basically the same code in the code behind.

Using the ashx, it'll look something like this:

<script type="text/javascript">
  $(document).ready(function(){
      $("#txtSearch").autocomplete('autocompletetagdata.ashx');
  });      
</script>

[WebService(Namespace = "http://www.yoursite.com/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class AutocompleteTagData : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        // Note the query strings passed by jquery autocomplete:
        //QueryString: {q=a&limit=150&timestamp=1227198175320}

        LookupTagCollection tags = Select.AllColumnsFrom<LookupTag>()
            .Top(context.Request.QueryString["limit"])
            .Where(LookupTag.Columns.TagDescription).Like(context.Request.QueryString["q"] + "%")
            .OrderAsc(LookupTag.Columns.TagDescription)
            .ExecuteAsCollection<LookupTagCollection>();

        foreach (LookupTag tag in tags)
        {
            context.Response.Write(tag.TagDescription + Environment.NewLine);
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}
bdukes
  • 152,002
  • 23
  • 148
  • 175
  • 1
    Did you have issues with permissions in IIS for the ashx file? I used this method and when I deploy the application to IIS 6, the browser can't access the ashx file. – Gromer Aug 18 '09 at 16:35
  • I think the new jQuery AutoComplete uses the property "term" instead of "limit", usless this code is based off of a different plug-in. From jQuery API docs: *"A request object, with a single property called "term", which refers to the value currently in the text input. For example, when the user entered "new yo" in a city field, the Autocomplete term will equal "new yo".* http://jqueryui.com/demos/autocomplete/#remote – atconway Sep 08 '11 at 18:26
  • `limit` is the maximum number of results to return. However, it might send `term` instead of `q`, which this example uses... – bdukes Nov 08 '11 at 19:10
10

I just posted this on another question, but you can override the parse function on the jQuery autocomplete plug-in to make it support any output.

Example:

    $("#<%= TextBox1.ClientID %>").autocomplete("/Demo/WebSvc.asmx/SuggestCustomers", {
        parse: function(data) {
            var parsed = [];

            $(data).find("string").each(function() {
                parsed[parsed.length] = {
                    data: [$(this).text()],
                    value: $(this).text(),
                    result: [$(this).text()]
                };
            });
            return parsed;
        },
        dataType: "xml"
    });

All this expects is a string array to XML... Very easy to do... If your using SubSonic, you should check out the RESTHandler (It's a hidden GEM!!!), it supports basic queries on all your objects and can return JSON/XML. Here is an example query using it...

/Demo/services/Customers/list.xml?CustomerName=JOHN

If you change list.xml to list.json it will change the results to JSON. The request above will return the a strongly typed "Customer" entity. You can change the parameter to support LIKE, NOT LIKE, etc... Very powerful and all the plumbing is arleady done...

Here is a video on it: http://subsonicproject.com/tips-and-tricks/webcast-using-subsonic-s-rest-handler/

Zachary
  • 6,522
  • 22
  • 34
  • This is the actual answer to handle XML with a name/value pairing. The marked answer above is only for the display text - you can't set/get a value pairing. – eduncan911 Jul 15 '09 at 02:36
6

The web service or a WCF service will give you the potential for a better interface. Both can also be set up to do Json serialization.

Since I'm taking a WCF class as I write (I'm on a break, really!), I'll sketch the WCF method.

[OperationContract]
[WebInvoke(RequestFormat=WebMessageFormat.Json,
           ResponseFormat=WebMessageFormat.Json)]
public LookupTagCollection LookupTags( int limit, string q )
{
     return Select.AllColumnsFrom<LookupTag>()
                  .Top(limit)
                  .Where(LookupTag.Columns.TagDescription)
                  .Like(q+ "%")
                  .OrderAs(LookupTag.Columns.TagDescription)
                  .ExecuteAsCollection<LookupTagCollection>();    
}

LookupTagCollection needs to be Serializable.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • Unfortunately, the Autocomplete widget doesn't expect JSON, it just wants text, each item on a new line. But for JSON stuff that looks nice. What URL would you use to access that? – bdukes Nov 21 '08 at 15:20
4

Jquery 1.8 Autocomplete uses "term" not "q" as the querystring param. this is the short and sweet version that I implemented. Hope this helps someone.

Javascript:

$(function () {
    $("#autocomplete").autocomplete({
        source: "/pathtohandler/handler.ashx",
        minLength: 1,
        select: function (event, ui) {
            $(this).val(ui.item.value);
        }
    });
});

ASHX Handler:

public class SearchHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        var term = context.Request.QueryString["term"].ToString();

        context.Response.Clear();
        context.Response.ContentType = "application/json";

        var search = //TODO implement select logic based on the term above

        JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
        string json = jsSerializer.Serialize(search);
        context.Response.Write(json);
        context.Response.End();
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}
anthonyvscode
  • 995
  • 8
  • 15