Thursday, April 3, 2014

Application-Wide JavaScript Autocomplete for Rails

For my skills application site I wanted to add some more front-end JS code, but wanted to start with something simple.  So the first thing I could think of that was pretty easy to do was adding an autocomplete to a couple of the search boxes.  The search boxes allowed  you to search based on the skill name.  Unfortunately there are over 1500 skills.  So making sure that you query the right skill is hard to do.

To create the autocomplete I decided just utilize jQuery UI's autocomplete control.  For my initial pass I decided to not use the service based approach and go with a static source of values.  To access these values I decided I would put the list of values in a data attribute on the actual autocomplete control itself.  The JavaScript would just then pull those values from that attribute.

Here is what I came up with:

  <%= text_field_tag :skills, params[:skills], :id=> 'skills_autocomplete',
        :data => {:skills =>  SkillTotal.retrieve_skills() } %> 
<% content_for :javascript do %>
  <script type='text/javascript'>
    $(document).ready(function(){
      $("#skills_autocomplete").autocomplete({
        source: $("#skills_autocomplete").data("skills")
      });
    });
  </script>
<% end %>

It ended up working out pretty well, however in my form JS I was querying the control by its ID.  Every time I needed an autocomplete I would essentially have this code on each one of my views.  I decided that this was good, but I could do better.  I wanted to make it more DRY.

I created a global.js file where I would put an on load function and set up the autocomplete so I didn't have to put it on each form.  Instead of going by the ID, I went by a class as well.  In the end it looked like this:

<%= text_field_tag :skills, params[:skills], :class=> 'autocomplete',
        :data => {:autocomplete_data =>  SkillTotal.retrieve_skills() } %>
(function(global){
    global.App = global.App || {};
    global.App.autocomplete = function(){
        $(".autocomplete").each(function(){
            var field = $(this);
            field.autocomplete({
                source: field.data("autocomplete-data")
            });
        });
    };
}(this));

$(document).ready(App.autocomplete);
I soon found out that my code wasn't working.  A little bit of digging and this has to do with the turbolinks gem which is included by default with Rails.  A little more digging and I now my JS looks like this and is running on multiple pages.

(function(global){
    global.App = global.App || {};
    global.App.autocomplete = function(){
        $(".autocomplete").each(function(){
            var field = $(this);
            field.autocomplete({
                source: field.data("autocomplete-data")
            });
        });
    };
}(this));

$(document).ready(App.autocomplete);
$(document).on('page:load', App.autocomplete);