Getting Started

JQuery UI's new Autocomplete is a great addition to its library of widgets. Autocomplete is amazingly well thought and customizable.

We had been using other plugins that worked well, but it made sense to swap them out for a couple reasons:

  • It's one less file to download or package for your site
  • It is highly versatile and integrates with other widgets with ease.

Setting up the Autocomplete with Rails was a little tricky at first, but once you are familiar with how it works, it is quickly done.

We highly recommend reading through the Autocomplete documentation to get a grasp on the methods and options. The examples run from a PHP script, but the JS is well commented.

What Makes the Setup Tricky

Well, it's only tricky if you are used to the previous JQuery Autocomplete plugins. Most of them expected the data as a JS response, but the new autocomplete uses JSON. The JSON is very welcome, however, because it allows you to send additional data like a thumbnail picture, or category in the response. This makes it very easy to modify the structure of the suggested results, but we'll get there soon.

Quick JQuery Setup

We like to link the JQuery files from Google. They cache well, and chances are the user has already downloaded them from another site.

Just place them above the body tag. (these examples are using Haml)

    %script{ :type => "text/javascript", :src => 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js'}
    %script{ :type => "text/javascript", :src => 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js'}

Be sure to be using the latest stable release for both. As of this article they are 1.4.2 and 1.8.2 respectively.

Generate a Controller for the Autosearch

It makes it simple and clean to have an autocomplete route, so depending on your rails version:

Note - We did this for a Rails 2.3.8 app, so take the Rails 3 commands as suggestions :)

You can read more about Rails 3 by visiting the Rails 3 guides website.

    #autocompete_searches_controller.rb
    
    #Rails 2
    script/generate controller AutocompleteSearches Index
    
    #Rails 3
    rails g controller AutocompleteSearches Index

And then add the route in your routes.rb file:

    #Rails 2
    
    #We like using the :only option if you do not need the other routes.
    
    map.resources :autocomplete_searches, :only => [:index], :as => 'autocomplete'
    
    #Rails 3
    resources :autocomplete_searches, :only => [:index], :as => 'autocomplete'
    

Setup the Search Query

With the controller.rb and route added, the search query can be as easy as a simple search query in sql, or in tandem with a full search-text engine like Sphinx.

Inside the autocomplete_searches_controller.rb lets add:

    #Rails 2
    
    def index

      @some_models = SomeModel.limit(10).search_for_name(params[:term])

      respond_to do |format|
        format.js
      end

    end

Basically, this just limits the findings to 10, orders them by name, and calls the simple search.

If you need more info on setting up a quick simple search or form, see Ryan Bate's screencast on Simple Searches

If you are using Sphinx, you can alternatively do:

    #Rails 2
    
    def index

      @some_models = SomeModel.search params[:term],
                                      :limit => 5,
                                      :match_mode => :any,
                                      :field_weights => { :name => 20, :description => 10, :reviews_content => 5 }
      respond_to do |format|
        format.js
      end

    end

Or with Rails 3

    #Rails 3
    
    respond_to :js

    def index
        @some_models = SomeModel.limit(10).search_for_name(params[:term])
        respond_with(@some_models)
    end
 

The main point is that you want to respond with JS and a couple named scopes can focus the search well.

On a side note - if you are using Heroku, you might want to lowercase the search term so that the query is Mysql and Postgresql compatible.

  #Rails 2
  named_scope :limit, lambda { |limit| {:limit => limit} }

  named_scope :search_for_name, lambda { |term| {:conditions => ['lower(name) LIKE ?', "%#{term.downcase}%" ]} }

  #Rails 3

  def self.search(q)
    [:name, :description].inject(scoped) do |combined_scope, attr|
      combined_scope.where("some_models.#{attr} LIKE ?", "%#{q}%")
    end
  end
  

For a lot more details on Rails 3 scopes and queries, see Ryan Daigle's Post on Rails 3 scopes.

Setup the JS Index View

With all the other rails pieces in place, you have the means to send the query, search the database, and respond with js.

In order to return JSON, we appreciated Scott Motte's suggestion to use a JS view and then use the to_json method instead of compiling the JSON directly.

You can always respond with JSON directly instead, but we use JS more and letting Rails put it together was more pragmatic.

Again, we are using Haml instead of erb.

    / index.html.haml for the Autocomplete Controller
    
    - data = []
    - @projects.each do |p|
      - data << {:label => "#{h(p.name)}", :value => "#{h(p.name)}" }
    = data.to_json

This view creates a JSON object with all the search results ready to roll. The Autocomplete widget will then use the label in the search result drop down and the value will be inserted into the search field.

Activating the Text field

There are a lot places where you could place this code depending on your style. Either an app.js or a controller specific js file will do the trick. We like the latter if there is a lot of specialized functions.

You can use the unique id from the text field or set a class as the js selector. The below assumes you already have a search form, but you are not limited to using it there.

We added the class "auto_search_complete" to the text field.

- form_tag '/search', :method => 'get', :id => 'search_form', :class => 'search' do
    %p
        = text_field_tag :term, params[:term], :class => "auto_search_complete"
        = submit_tag "search", :name => nil, :class => 'button', :id => "search_btn"

Setting up the JQuery Call

A quick note, JQuery UI expects by default the param to be called "term." You can change that in the options.

This is where we love the new widget. You can almost format the results however you wish. The vanilla version is easy, but you can get custom results with ease.

In your .js file for your JQuery, you can start with:

    $(".auto_search_complete").autocomplete({
        source: "autocomplete.js",
        minLength: 3
    });

You can view the full options on the UI Autocomplete documentation.

You can also add options, or customize the source by using a function instead of just a path if you want to customize the date response:

$('.auto_search_complete').autocomplete({
    minLength: 3,
    delay: 600,
    source: function(request, response) {
        $.ajax({
            url: "/autocomplete.js",
            dataType: "json",
            data: {term: request.term},
            success: function( data ) {
                response( data );
            }
        });
    }           
});

You can even go really crazy and customize the widget to add categories to the search results or images or cache the results.

Again, the UI Autocomplete documentation has a lot of great examples of how to deal with the JS in various ways like:

$.widget("custom.catcomplete", $.ui.autocomplete, {
    _renderMenu: function( ul, items ) {
        var self = this,
        currentCategory = "";
        $.each( items, function( index, item ) {
            if ( item.cat != currentCategory ) {
                ul.append( "
  • " + item.cat + "
  • " ); currentCategory = item.cat; } self._renderItem( ul, item ); }); } }); var auto_cache = {}; $('.searchInput').catcomplete({ minLength: 3, delay: 650, source: function(request, response) { if ( request.term in auto_cache ) { response( auto_cache[ request.term ] ); return; } $.ajax({ url: "/auto-search.js", dataType: "json", data: {search: request.term}, success: function( data ) { auto_cache[ request.term ] = data; response( data ); } }); } });

    .widget will allow you to customize the autocomplete and append additional li's or other tags by creating a new ui function based on the default autocomplete. You can then call your custom version, add a cache variable to hold the responses, and display you additional data.

    Adding additional data is as simple as adding to the JSON object in the Autocomplete controller index view:

    - data = []
    - @projects.each do |p|
      - data << {:label => "#{h(p.name)}", :value => "#{h(p.name)}, :cat => "#{h(p.category)}, :img => "#{h(p.some_thumb_path)}" }
    = data.to_json
    

    The Beauty of it all

    Our favorite feature is being able to combine the autocomplete with other widgets. Need to have the search results pop up in a dialog box? You can do that. Need to select multiple results to fine tune or filter data? You can do that too. It's all possible because you can chain together the autocomplete with custom .ajax functions.

    Styling the Drop Down

    The UI widget even has its own unique classes if you need to alter or override your general UI Theme.

    ui-menu, ui-menu-item, and ui-autocomplete

    The rest is styled from the UI CSS. We recommend building a theme at the JQuery UI themeroller, or customizing one of the defaults, then using the widget specific classes to fine tune the look and feel as needed.

    Thank you to the JQuery UI Team

    A big thanks to the JQuery UI Team for putting together a great addition to the library. It really makes for a great autocomplete solution.

    The Autocomplete in Use

    If you want to see what we did with the Autocomplete widget in production, you can check out Aboutredlands.com's hompage or try out the Road Tortoise beta