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

jQuery SearchBox AutoComplete Widget

4.80/5 (10 votes)
28 Jun 2011CPOL2 min read 66.8K   3.1K  
jQuery widget for autocompletion of categorized data through the hierarchies

Introduction

SearchBoxAutoComplete is auto-completion jQuery widget that enables you to search the categorized data through the hierarchies, i.e., Brand/Categories/Products or Category/Brands/Products.

Background

Many Virtual Stores are offering Products classified by Categories and Brands. Usually there are a lot of Categories, Sub-Categories and Brands, and a user has difficulties to find the appropriate Category, Brand or Product.

SearchBoxAutoComplete widget enables a user to quickly find and select the wanted Category or Brand. By entering 2 or more characters, the user can filter down the list to better matches.

SearchBoxAutoComplete1.PNG

After user selects Category, the widget offers a drop-down list with all the Brands that products of the selected Category belong.

If user selected the Brand, widget would offer drop-down list with all the Categories that Brand’s Products belong to.

SearchBoxAutoComplete2.PNG

User can select multiple items from the offered list, or enter new characters to start a new hierarchy. Results from multiple hierarchies are aggregated at the server.

Here is the sample of 2 hierarchies: categ1: brand1: brand2; categ2: brand3:

SearchBoxAutoComplete jQuery Widget

jQuery Widgets resolve the problem that jQuery Plugins have with associating an JavaScript Object with a DOM element, avoiding circular references and memory leaks.

SearchBoxAutoComplete jquery.ui.searchboxautocomplete.js” widget uses standard “jquery.ui.autocomplete.js” widget.

Instead of hacking the existing widget, SearchBoxAutoCompelete modifies the behavior of the existing widget by extending or overriding/sub-classing the widget properties, methods and events.

JavaScript
$.widget("ui.searchboxautocomplete", $.ui.autocomplete, {
	_create: function () { 
		...
	},
 	_init: function () {
		// call the base class
		$.ui.autocomplete.prototype._init.apply(this, arguments);
		this.element.bind("searchboxautocompleteclose", function () {
			self.options.offeringSecondLevel = false;
		});
		…
	}, 
	options: { 
		select: function (event, ui) { … },
		cache: {},
		autocompleteRows: [],
		separator: ": ",
		…
	} 
}

SearchBoxAutoCompleteRows widget uses Ajax JSON call to get the data from the server.

List of: AutocompleteRow { category: 'Brands', label:'Bikes', value:11 }
is serialized at the client by:

JavaScript
JSON.stringify(this.options.autocompleteRows).

At the server, it is deserialized with:

C#
var selectedRows = new JavaScriptSerializer().Deserialize<List<AutocompleteRow>>(acRows); 

Form

HTML
<form id="searchbox" action="/search/productlist" method="post">
   <input type="text" id="search" value=""/> 
   <input id="searchbutton" type="submit" value="Search"/> 
   <input type="hidden" id="selectedAutocompleteRows"  />
</form>  
JavaScript
$("#search")
    .setSearchText(autocompleteRows)
    .searchboxautocomplete({
        autocompleteRows: autocompleteRows,
        source: function (request, response) {
            var term = $.ui.searchboxautocomplete.extractLast(request.term);
            var data = this.options.getCachedData(term);
            if (data != null) {
                response(data);
                return true;
            }
            var that = this;
            this.options.lastXhr =
                $.getJSON("/Search/GetBrandsAndCategories",
                    {term:term, acRows:JSON.stringify(this.options.autocompleteRows)},
                     function (data, status, xhr) {
                        that.options.setCachedData(term, data);
                        if (xhr === that.options.lastXhr) {
                            response(data);
                        }
                    })
        },
        search: function () {
            if (term.length < 2) {
                return false;
            }
        }
    })      

Data are received from server by Ajax $.getJSON() call. Arguments sent to the server are:

  • Last search term
  • Array of already selected rows

This is done by:

JavaScript
{ term: term, acRows: JSON.stringify(this.options.autocompleteRows) } 

Data returned from the server are rendered by function response(data) called upon successful Ajax call.

Default fields are: category, label, value. You can override _renderItem function and display your fields.

Data returned from the server are in JSON format, something like this:

JavaScript
var data = [
{category:"Categories", label:"Mountain Bikes", value:"1"},
{category:"Categories", label:"Road Bikes", value:"2"},
{category:"Categories", label:"Touring Bikes", value:"3"},
{category:"Brands", label:"Mountain Bikes", value:"12"}
]

Here is the MVC 2.0 server method that returns the data in JSON format:

C#
public JsonResult GetBrandsAndCategories(string acRows, string term)
{
     var selectedRows = new JavaScriptSerializer()
           .Deserialize<List<AutocompleteRow>>(acRows);
    var firstRow = selectedRows[0];
    term = term.Trim().ToLower(); 
    if (term == "secondlevel")
    {
        var list = new List<AutocompleteRow>();
        if (firstRow.category == "Brands")
        {
            var brand = Brand.GetById(int.Parse(firstRow.value));
            list = brand.Categories
                .Select(c => new AutocompleteRow { 
                    category = "Categories", label = c.Name, value = c.Id.ToString() 
                })
                .ToList<AutocompleteRow>();
        }
        else
        {
            // . . . 
        }
        return Json(list, JsonRequestBehavior.AllowGet);
    }
}  

Demo Data Repository

The structure of data is usually like this:

XML
 <categories> 
   <category id="3" name="Clothing">
      <sub-categories>
         <sub-category id="22" name="Shorts">
            <products>
               <product id="841" name = "Men's Sports Shorts, S">
                  <brands>
                     <brand id="29" name="Integrated Sport" price="20.00">
                     <brand id="102" name="Fitness Association" price="19.80">
                  </brands>
               </product>
            </products>
         </sub-category>
      </sub-categories>
    </category>
</categories>  

History

  • 28th June, 2011: Initial post

License

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