Sunday, January 29, 2012

Rails / Ajax / Autocomplete

As you all know if you're regular readers here, I started out in the Ruby web framework world with Ramaze and I'm definitely glad that I did. However, it doesn't seem like there's much of a job market for Ramaze programmers, so I've taken up using Rails, specifically Rails 3.1. There's a number of great books for Rails and the one I'd recommend is Ruby on Rails 3 Tutorial by Michael Hartl. In this book, he develops a Twitter like application in Rails starting with basic pages and moving on to more complex topics.

I've been working on a simple project for a greeting card application and I came up with a little something that might be interesting. The cards have a recipient and can have multiple signers. I added autocomplete for the recipient, but it was a bit harder for the comma separated list of signers. What I ended up doing was was simply grabbing the last piece of the list in the controller and passing that back to the view.

Here's the view code ...


Create a Card


<%= image_tag "card_images/#{@template.image_name}", :size => "200x200" %>
<%= form_tag ("create_from_image") do %>

<%= label_tag("Add the recipient's email: ") %>


<%= text_field_tag(:recipient_email, "#{@recipient_email}")%>



<%= label_tag("Add A Greeting: ") %>


<%= text_field_tag(:greeting, "#{@greeting}")%>



<%= label_tag("Add the signers' emails (comma separated): ") %>


<%= text_field_tag(:signers_email, "#{@signers}")%>



<%= hidden_field_tag :template_id, @template.id %>

<%= submit_tag("Preview the card!") %>
<%= submit_tag("Send the card!") %>
<% end %>



There's nothing very interesting in here, but the main field we're interested in is the :signers_email text field.

Here's the javascript code for it ...


$(document).ready(function(){
// Below is the name of the textfield that will be autocomplete
$('#recipient_email, #signers_email').autocomplete({
// This shows the min length of charcters that must be
// typed before the autocomplete looks for a match.
            minLength: 2,
// This is the source of the auocomplete suggestions. In this case a
// list of emails from the users controller, in JSON format.
            source: '/users/index.json'
})

});


What this says is that for both the recipient_email and signers_email fields, use autocomplete, send when we have two characters, and use the /users/index.json function to get the data we need.

Finally, we have the controller code ...


def index
@title = "All users"
@suggestion = "Pick someone and send them a card!"
if params[:term]
search_term = params[:term].split(",").last.strip
@users = User.find(:all, :conditions => ['email LIKE ?', "#{search_term}%"])
@users_hash = []
@users.each do |user|
@users_hash << { "label" => user.email }
end

else
@users = User.where(:active => true).paginate(:page => params[:page])
end

respond_to do |format|
format.html

# Here is where you can specify how to handle the request for "/people.json"
format.json { render :json => @users_hash }
end
end


Here, we have a couple of things going on ... for the Ajax side, we'll get the params[:term] field so we know that we need to get the constrained list. First, we're going to grab the params[:term] value, which should look something like "abc@test.com, def". Here we'd like to look for emails that start with the "def" and ignore the "abc@test.com" piece. So ... we split on the comma, grab the final element in the array, and then remove any leftover spaces at the end or beginning of the string. Next, we'll create an array where each element is a hash of the form { "label" => "defghi@test.com" }. This is the form that the jquery autocomplete needs. Finally, we use the respond_to section to send this back as json.

As always, let me know if you have questions.