I recently had to add some auto completion behavior to a few text fields in a Rails project. Rails used to have a view helper for this baked-in, it’s a plugin now, but I never really liked working with it. I hate farting <script> elements throughout my HTML for functionality like this, it looks dirty and just feels wrong to me.

My goal was to go from something nasty like this:


<input type='text' name='school[name]' id='school_name_1' />
<div class='autocompleter-choices' id='school_name_1_completer'></div>
<script type='text/javascript'>
//<![CDATA[
new Ajax.Autocompleter(
  'school_name_1',
  'school_name_1_completer',
  '/schools/autocompleter/search',
  { 'paramName' : 'q' }
);
//]]>
</script>

To something simple and elegant like this:


<input type='text' name='school[name]' autocompleter='/schools/autocompleter/search' />

My solution was to do it unobtrusively with some straight-forward Javascript:


Helpers.TextFieldAutocompleters = {

  init : function()
  {
    $$('input[autocompleter]').each(function(input) {
      var url = input.readAttribute('autocompleter');
      var container = new Element('div', { 'class' : 'autocompleter-choices' });
      input.insert({ 'after' : container });
      new Ajax.Autocompleter(input.identify(), container.identify(), url, {
        'paramName' : 'q'
      });
    });
  }

}

Event.observe(window, 'load', function() {
  Helpers.TextFieldAutocompleters.init();
});

Lets have a quick look at what I am doing here:

  • I use Prototype’s $$ utility method to grab all of the <input> elements on the page that have an autocompleter attribute.
  • I insert an anonymous <div> below each <input> to contain the <ul> of results returned by the server.
  • I use Prototype’s Element.identify() method to automatically generate DOM IDs for the anonymous/potentially anonymous container <div> and <input> elements.
  • I create a new Scriptaculous Ajax.Autocompleter that will submit to the path indicated in the <input> element’s autocompleter attribute and use ‘q’ as the param name.

That’s all there is to it!

The Rest Of It

This is the controller action:


def autocompleter_search
  @schools = School.name_like(params[:q]).all(:limit => 8)
  t = []
  t << '%ul'
  t << '  - @schools.each do |school|'
  t << '    %li= highlight h(school), h(params[:q])'
  template = t.join("\n")
  respond_to do |format|
    format.js   { render :inline => template, :type => :haml }
    format.html { render :inline => template, :type => :haml }
  end
end

The named_scope I'm using for the above School finder:


named_scope :name_like, lambda { |name|
  { :conditions => ['`name` LIKE ?', "%#{name}%"],
    :order => '`name` ASC' } }

And the styles I use for the choices list:


.autocompleter-choices
  :background #fff
  :border 1px solid #8caddd
  :margin-top -1px
  ul
    :padding 2px
    li
      :padding 3px
      em
        :font-weight bold
        :font-style normal
      &.selected
        :background #f6eda9

That didn’t hurt one bit!