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 anautocompleterattribute. - 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’sautocompleterattribute 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!
Andrew Brown
December 17, 2008 22:33
Rails never had a good built in one. I had to build my own as well, but your code is much more condense than mine
Geoffrey Grosenbach
December 17, 2008 23:54
This would make a great LowPro behavior.