Ajaxariffic Autocomplete with Scriptaculous

You’ve read the ONLamp tutorial, but you lust for more. So you want to autocomplete, do ya? Do ya??

You’ve come to the right place.

        <span id="more-3930"></span>

Into JavaScript? Have I got good news for you!

If you’re interested in JavaScript-driven web apps, snazzy visual fx, and generally confusing people into thinking your site is Flashβ€”but oh-so-much betterβ€”you should buy our JavaScript Performance Rocks! book while it’s still in beta. Written by Thomas Fuchs, the creator of Scriptaculous, and yours truly, the maker of funny jokes and shiny graphics.

We cover everything from The Most Ridiculous Performance Fix Ever (and it is ridiculous), to serving strategies, DOM diets, loop unrolling (for those really extreme cases), how to play nice with the garbage collector, and everything in between. And it comes with our custom profiling tool, The DOM Monster, which analyzes your pages and suggests fixes. This package is only $24 right now but will be $29 as soon as it’s done, in mid-June 2009… but if you snag your copy today, you get the final version by email just as soon as it’s ready and save a noteworthy 5 bux! You can’t lose.

Autocompletin’ in Rails

With the magic of Prototype.js and Scriptaculous, the autocomplete is, in theory, extremely easy. But throw a lack of good documentation into the mix and something easy and “magical” and Bad Things Happen.

First Things First

One, check out the autocomplete demo. It’s one of the more “magical” Ajax goodies because it automatically handles the appearance of the floating div, the keyboard navigation, the ability to click your choice with the mouse or use Tab or Enter to complete your entry.

Is that what you want? If not, you’ll need to find another solution, because this stuff’s all built in—you can’t do an autocomplete with this code and get it any other way (although you can style it differently, to be sure). The pixie dust is non-negotiable.

Secondly, you’ll need to get the very latest version (download link) of the Scriptaculous script files and also their patched version of Prototype.js. If you’re using older versions of either software and having problems, this may be the reason.

The Simplest Autocomplete

You can almost scaffold autocompletion, as you can see in this very simple demo (also an official Scriptaculous demo). If you don’t want to do anything fancy, this may be the way to go. At least you can see near-instant results this way.

The source for this is:


# view
<%= text_field_with_auto_complete :contact, :name %>

# controller
auto_complete_for :contact, :name

In the view, you’re using the built-in Rails helper text_field_with_auto_complete, which takes two arguments: a symbol for the model and a symbol for the method (column) of the model you want to autocomplete on.

Other examples of this helper might be:

<%= text_field_with_auto_complete :book, :title %>
<%= text_field_with_auto_complete :user, :username %>
<%= text_field_with_auto_complete :user, :email_address %>

This not only does some Ajax magic, sending an Ajax request to your controller, but it creates a field appropriate for Rails use when creating or editing an instance of a given model, just like a normal form helper.

In the controller, you’re using the method auto_complete_for which, surprisingly, takes exactly the same arguments as the widget in the view. Hey, that’s not all that tough at all.

It probably goes without saying that you’ll need to be working off a database model with actual, you know, content in it for this to work. But I guess it really doesn’t go without saying since I just said it. Oops. There goes that idea.

Mr. Ajax Fancypants

Of course, once you taste the hors d’oeuvres (can I buy a vowel? what do you mean, we’re out?), your tummy starts grumbling for dinner. Man cannot live on miniature quiches alone.

There are just a few rules you need to know about the Rails magic in order to create your own fancypants autocompletes to inspire those oohs and ahhs.

The View

If you’re wanting to provide autocompletion for a field that belongs to a model, for example in an add or edit screen, you’ll want to use the same old helper in the view as you would for the dead-simple autocomplete:


# view
<%= text_field_with_auto_complete :cookie, :flavor %>

If you want to do something like a live search, see the heading “Roll Your Own” a little further down.

Be Controlling

When you don’t wanna go straight to the model for the dish, you’ll want to avoid the scaffolding and write your own method in the controller. There’s some magic here, too, since Rails is going to expect a method called auto_complete_for_cookie_flavor—the last two items being the arguments you gave to text_field_with_auto_complete. Phew.

To take a nod (and some slightly modified code) from Scriptaculous, here’s what you might do in your controller:


def auto_complete_for_cookie_flavor
  @cookies = Cookies.find(:all,
    :conditions => [ 'LOWER(flavor) LIKE ?',
    '%' + params[:cookie][:flavor].downcase + '%' ],
    :order => 'name ASC',
    :limit => 8)
  render :partial => 'cookies'
end

As you can see, auto_complete_for_cookie_flavor is a custom controller method which searches for cookies matching the flavor the user has been in the process of typing. This method will get called every time someone presses a key in the cookie flavor autocomplete field so the result will get ever more filtered. Sweet.

I’m Rather Partial to Lists

But we’re not done yet… you need to have that cookies partial. And it has to be crafted a certain way, because the Javascript powering all the magic is very particular.

Here’s what the view partial might look like:

# filename: _cookies.rhtml
<ul class="cookies">
<% for cookie in @cookies do -%>
  <li class="cookie">
    <div class="image">
    <img src="/images/cookie/<%= cookie.id.to_s[-1,1] %>.jpg"/>
    </div>
    <div class="name"><%=h cookie.name %></div>
    <div class="flavor">
      <span class="informal"><%=h cookie.flavor %></span>
    </div>
  </li>
<% end -%>
</ul>

This is because the Javascript expects only a UL—an unordered list—and its contained list items (and you have to make sure to write good XHTML and close all your tags, including <li></li>). So anything fancy that you can do within the confines of the list item tags, you can probably get away with.

If you need more help, load up your site in FireFox, play with the autocomplete for a second or two, then open the DOM Inspect from the Tools menu. It’ll show you the autocomplete div even if it’s currently hidden, and you can find out the class names and ids of any contents.

Roll Your Own

If you want to work off a field that does not belong to a column in one of your models, or don’t want the drop-down/keyboard nav/tab completion magic, you will want to use auto_complete_field instead, like thus:

<input type="text" name="snacks">
<div id="snacks_auto_complete"></div>
<%= auto_complete_field 'snacks', :url => {:action => 'complete_me'} %>

The auto_complete_field method lets you specify your own controller and method to get the autocomplete results, and it automatically assumes you want the data to be populated in a div or other HTML element with the id that equals the name of the input field plus _auto_complete.

Remember that this won’t provide all the fancy auto-magic with the drop-down div and select stuff. But it also means you can craft a response in any format you like, not just a UL.

Stylin’

Whether you use the fancy custom goodies above or the original easy-peasy scaffolding, you’re never quite done til everything looks gorgeous and high-tech and Web2.0-worthy. But your list will look like—I’m just going to be blunt here—crap if you don’t use the powers of CSS. You may not even be able to see your autocomplete results because they might not be floating on top of your content, as they’re supposed to.

Here’s some sample CSS that I, uh, borrowed from Scriptaculous’ demo. This doesn’t cover the above fancy stuff, but it will get you started, because anything you output will get wrapped in a div called autocomplete and there are other things you can count on, such as the list item that your user is currently on—whether via keyboard arrow keys or mouse movement—being given the class selected.

<style type="text/css">
    div.auto_complete {
      position:absolute;
      width:250px;
      background-color:white;
      border:1px solid #888;
      margin:0px;
      padding:0px;
    }
    li.selected { background-color: #ffb; }
  </style>

58 Comments

  1. ichigo says:

    yet another great rails article…thanks a lot…it was del.icio.us πŸ˜€

  2. BigDaddy71 says:

    I’ve been ignoring Web 2.0 for WAY too long, and this is yet another example of what you SHOULD be doing instead of following some old, stuffy guidelines.

    The autocomplete stuff is something that would probably put an interesting amount of load on your system (like DB queries to pull info out, for example) but it would make the user experience a lot better. In my day job, we’ve been converting over a country-state-city dropdown sequence to be AJAXy, and while the load hasn’t dropped in terms of database stuff, it’s less load to have pages constantly refreshed.

    Besides, you can never have enough documentation on how to do ANYTHING remotely complicated.

  3. GreasyGreasy says:

    Wonderful. And timely, for my next project. Thank you.

  4. technoweenie says:

    Where was this a few days ago when I was pouring over the scriptaculous docs??? Good stuff. I just need to figure out how to autocomplete based on a locally loaded JS array…

  5. Ross says:

    Awesome article πŸ˜€

  6. Adept says:

    Ajax.net — the best!

  7. Mendokusee says:

    Hi there. Take a look on some interface clientside features of my sample autocomplete combotextbox:

    http://www.pixel-apes.com/rubrika/combotextbox/

    1. Highlighting found substring in matches
    2. Moving selection by up/down arrows

    It is completely clientside though (because it deals with &lt;select&gt; and written for some Greasemonkeys plugins)

  8. rob says:

    great tutorial !

  9. Sean says:

    You may want to double-check the links you’ve posted to Prototype and Script.aculo.us — I downloaded the linked versions in this article and couldn’t get the simplest example to work (display:none; on the results div never flipped), but a fresh overwrite from the ‘rails’ command (0.13.1) installed versions that worked happily with one another.

  10. Ben Hiller says:

    I tried to use this and with my normal browser, Safari, and it wasn’t working. I thought I would try it with Firefox, since I could look in the JS console, but for some reason it works in FF. Is this a Safari 2.0.1 bug, or a RoR bug, or a Scriptaculous bug?

  11. …taking off my heat and apload till there is a pain in my palms:)

    The only thing, took me a litle of confusion where to put

    auto_complete_for :contact, :name

    If you put it not right after the declaration of the controller but in the action, you will get a noMethodError.

    Other than that, simply great.

  12. Andrew says:

    Don’t forget to include <%= javascript_include_tag :defaults %>

    in your document.

  13. ScottiouS says:

    To Alex,

    Thanks so much for that quirks comment. I spent half the day trying to figure it out also. I had indents and it was only inserting the spaces and nothing else. I counted the spaces that were there and it was the same as an indent but I didn’t think it would "really" do that, so I didn’t test. Anyway thanks for this post, saved me more troubles πŸ™‚

    Another problem I had was that using the most recent scriptaculous files (v1.6) I could not get this tutorial to work. ?

  14. ScottiouS says:

    Ok fixed my last problem. I was using prototype 1.4 rather than the 1.5.0_pre1 that’s shipped with scriptaculous. Hmm.. quite silly.

  15. I use Firefox in Ubuntu πŸ™‚

  16. Gautam says:

    Scriptaculous is awesome n u ppl make it easier by explainin it your way. gr8!!!

  17. Gautam says:

    Scriptaculous is awesome n u ppl make it easier by explainin it your way. gr8!!!

  18. Well done, nice instructions!

  19. There is some strange behaviour with this.

  20. I thout to do it in my local version πŸ™‚

  21. I think it would be usefull for other users also.

  22. Try AutoAssist (http://capxous.com/autoassist/) for those who didn’t use ror

  23. Devin says:

    Any idea on how to combine it with something like this: http://mudabone.com/aietc/?page_id=410

    ?

  24. oomtuinstoel says:

    I have a select like this:

    <%= collection_select(:parent, :doctor_id, @doctors, :id, :name) %></p>

    that I want to replace with an autocomplete field. This is working:

    <%= text_field_with_auto_complete :parent, :doctor_id, {}, :skip_style => true %>

    BUT: The lookup field comes back with a string. How can I get an id in stead of this string?

  25. i am not sure as to why πŸ™

  26. I use Firefox in Ubuntu.

  27. Thats correct πŸ™

  28. The problem is my browser πŸ™‚

  29. Black Areola says:

    That is strange…

  30. Thanks for the write-up πŸ™‚

  31. I think it would be usefull for other users also…

  32. Thanks for the write-up πŸ™‚

  33. The content of your show is great, I really enjoy it!

  34. I thout to do it in my local version πŸ™‚

  35. Well at last catched the problem πŸ™

  36. I was very dissapointed of this πŸ™‚

  37. Steven Koch says:

    to add a id in auto_complete_field follow this tutorial, but add in view:

    search the manufacture

    —view—

    <%= hidden_field :product, :manufacture_id%> <%= text_field_with_auto_complete :manufacture, :name%>

    –Controler—

    model :manufacture def auto_complete_for_manufacture_name @manufactures = Manufacture.find_by_sql( "SELECT * FROM manufactures WHERE name LIKE ‘%"+params[:manufacture][:name].downcase + "%’ ORDER BY name ASC LIMIT 10") render :partial => ‘manuf’ end

    — _manuf.rhtml —

    <ul> <% for manufacture in @manufactures do -%> <li id="<%=manufacture.id%>" onclick="$(‘product_manufacture_id’).value=this.id"><%=manufacture.name%></li> <% end -%> </ul>

  38. Steven Koch says:

    Ha..!!! if it does not forget:

    in method …def show… and def edit.. @product = Product.find(param[:id]) @manufacture = Manufacture.find(@product.manufacture_id)

    this show the name of manufacture in auto text fiel…

    :p

  39. I thout to do it in my local version πŸ™

  40. Steven Koch says:

    but when it is wanted the id, the better choise is to use methods find_or_create_by_name(param[:name_field_autocomplete])

  41. Steven Koch says:

    search the manufacture and return id:

    view <= hidden_field :product, :manufacture_id> <= text_field_with_auto_complete :manufacture, :name>

    Controler

    model :manufacture

    def auto_complete_for_manufacture_name @manufactures = Manufacture.find_by_sql( β€œSELECT * FROM manufactures WHERE name LIKE β€˜β€œ+params:manufacture.downcase + β€œβ€™ ORDER BY name ASC LIMIT 10

  42. But I’m not sure why!

Hey, why not get a shiny
Freckle Time Tracking
account?