Fast Auto-completion with Rails, Scriptaculous and JSON

April 4, 2007

Inspired by the excellent Rails Recipes book , I created an improved Auto-completion helper, which uses JSON and AJAX instead of a script tag for loading the completions. What we want to achieve is a search field, which pops up immediately, showing us a list of possible completions for our search word. Look at Google Suggest to get an idea.

Rails Autocomplete

Rails already has an auto_complete_field, which sends an AJAX request for each keystroke. This approach is quite slow, but works in most cases, especially for large datasets auto_complete_field is the better choice. Our idea, stolen from the Rails Recipes book is to fetch the array of possible completions only once. Each keystroke will trigger only a local lookup and need no further server interaction.

Scriptaculous already has the right tool for this job: Autocompleter.Local. We will just pass a javascript array of possible completions to the constructor and we’re done.

CSS

OK, let’s start. First we need the CSS used by Autocompleter.Local, which styles the choices box:

  1. div.auto_complete {
  2. width: 350px;
  3. background: #fff;
  4. }
  5. div.auto_complete ul {
  6. border:1px solid #888;
  7. margin:0;
  8. padding:0;
  9. width:100%;
  10. list-style-type:none;
  11. }
  12. div.auto_complete ul li {
  13. margin:0;
  14. padding:3px;
  15. }
  16. div.auto_complete ul li.selected {
  17. background-color: #ffb;
  18. }
  19. div.auto_complete ul strong.highlight {
  20. color: #800;
  21. margin:0;
  22. padding:0;
  23. }

Controller

Rails already has an controller macro for generating a auto completion action. We will create a similar macro, which will generate an action, which in turn generates the JSON response. Sounds complex, but the implementation is quite easy. Just add to your ApplicationController:

  1. def self.fast_auto_complete_for(object, method, options = {})
  2. define_method("auto_complete_for_#{object}_#{method}") do
  3. render :json => object.to_s.camelize.constantize.find(:all).map(&method).to_json
  4. end
  5. end

The response of the generated action will now contain a list of all values for the desired attribute. You can use it like in your controllers: fast_auto_complete_for :sport, :name

Javascript Helper

Now let us get into the tricky part: the javascript macro helper. How will we get the completion list? Prototype includes the Ajax.Request class, which sends an Ajax Request to our generated action and fetches the array encoded as JSON. Furthermore we have to generate a div which will hold the popup list for our completion entries. Without going into detail, I’ll just show you the code, which you add to your ApplicationHelper:

  1. def fast_auto_complete_field(field_id, options={})
  2. div_id = "#{field_id}_auto_complete"
  3. url = options.delete(:url) or raise "url required"
  4. options = options.merge(:tokens => ',', :frequency => 0 )
  5. script = javascript_tag <<-end
  6. new Ajax.Request('#{url}', {
  7. method: 'get',
  8. onSuccess: function(transport) {
  9. new Autocompleter.Local('#{field_id}', '#{div_id}', eval(transport.responseText), #{options.to_json});
  10. }
  11. });
  12. end
  13. content_tag 'div', script, :class => 'auto_complete', :id => div_id
  14. end

Our helper needs the id for the text field we want to enhance. Based on this id the helper generates the div for presenting the completion entries. It is also required to pass the url of the json action, which is in our case /sports/auto_complete_for_sport_name.

Usage example

  1. <form>
  2. <input type="text" name="name" id="sport_name"/>
  3. <input type="submit" value="Search"/>
  4. </form>
  5. <%= fast_auto_complete_field :sport_name, :url => '/sports/auto_complete_for_sport_name' %>

Well, that’s it. Now you may enjoy snappy auto-completion and feel good about using bleeding edge technology like AJAX and JSON.


Posted in category Ruby by Matthias Georgi. Tagged with ruby, ajax, rails, scriptaculous, json.
Similar Posts