January 27, 2011

JQuery Autocomplete with hidden data

This is a problem that I've encountered many times before when building Rails apps, but didn't know how to solve elegantly until one of my friends pointed out the solution was directly in the JQuery Autocomplete documentation (in my defense, it was at the very bottom).

Imagine you have an autocomplete field in a form that populates some Rails model, for example user's names. Naively, you would just pass an array of names to the JavaScript method like

["Amber Feng", "John Doe", "Harry Potter"]

and when the form is submitted, individually re-query the ID's for each of the names:

name = params[:user]
user = User.find_by_name(name)

Now this is super inefficient, if you're using some method to populate the autocomplete with User obejcts in the first place, why don't you just attach the id to the autocomplete to be submitted instead of just the name?

def find_users
  render :json => User.all.map {|c| c.full_name }
end

def find_users
  render :json => User.all.map {|c| {:name => c.full_name, :id => c.id } }
end

Then, with your Javascript you can pass objects to the Autocomplete plugin (with full name AND id information) instead of just name information:

// when /find_users only passes an array of names
$.ajax({
  url: '/find_users',
  success: function(data) {
      $("#user_query").autocomplete(data);
  }
});

// when /find_users returns an array of objects
$.ajax({
  url: '/find_users',
  success: function(data) {
      $("#user_query").autocomplete(data, {
          formatItem: function(item) {
              return item.name;
          }
      }).result(function(event, item) {
          $("#user_id").val(item.id);
      });
  }
});

You may have noticed in that last example I was doing a little something extra with the .result() function.

This function is a handler I've attached to the query field that is called whenever a user selects an option from the autocomplete. Here, I'm simply populating a hidden field I've added to the form called #user_id so when the form is submitted, the id of the user is sent (instead of just the name).


# much faster than User.find_by_name!
id = params[:id]
User.find(id)

If you feel like this way is too hacky or Javascript just isn't your thing, there are Rails plugins available like Model Auto Completer, but I haven't tried any third party plugins/widgets so I can't comment on how well they work or how easy they are to integrate into your application.