November 21, 2008

Rails text_field_with_auto_complete and not displaying the selected item

Tags: Rails, Ruby, Technical

I am doing some work with the Rails text_field_with_auto_complete method to provide a dropdown list of completed options as a user types, like Google Suggest. However, I needed the item displayed in the dropdown to be different to the item displayed when it is selected. I couldn’t find any help online. So at first I thought I would need to override the AJAX code, but when I looked I saw text_field_with_auto_complete already had this feature built in.

To find out how to use text_field_with_auto_complete, I recommend this Railscast (I have watched about a dozen Railscasts and they have all been great). My starting setup is as described in the Railscast.

The trick to displaying something different to what was selected is to have the field to be displayed hidden in the selection list. To do this add a <div> with a class into the selection item with style='display:none'. Thus the text in the div will not be displayed in the dropdown, but the text is there. Put the text to show when an item is selected into the hidden div. In the file to create the list of selection items I had the following:

<% return unless @queues %>
<% items = { |entry| content_tag("li","<div class='wq_display' style='display:none'>#{} </div><div class='wq_full'>#{entry.name_and_owner}</div>") } %>
<%= content_tag("ul", items) %>

Then in call to the text_field_with_auto_complete method specify :select in the fourth argument. The value of the :select is the class name of the div to use for displaying. So I used the below and voila, the wq_full div is displayed in the dropdown and the wq_display div is displayed in the text box when selected!

Queue <%= text_field_with_auto_complete :add, :queue, 
       {:size => 20, :maxlength => 40 }, 
       {:url => formatted_workqueues_path(:js), :method => :get, :param_name => 'search', :select =>  'wq_display'} %>

This concept can be easily extended. I also needed a third field, one containing the selection item’s id and have this passed back to the server on submit. Thus I just add a new hidden div to the selection items containing the id:

<% items = { |entry| content_tag("li", 
	"<div class='wq_display' style='display:none'>#{}</div><div class='wq_full'># {entry.name_and_owner}</div><div class='wq_id' style='display:none'>#{}</div>") } %>
<%= content_tag("ul", items) %>

Then I use :after_update_element to provide a javascript function that runs after an item is selected. Here it sets the value of a hidden input field to the text inside the new div.

Queue <%= text_field_with_auto_complete :add, :queue, 
       {:size => 20, :maxlength => 40 }, 
       {:url => formatted_workqueues_path(:js), :method => :get, :param_name => 'search', :select =>  'wq_display',
          :after_update_element => "function(element,val) {
            var nodes ='.wq_id') || [];
              $('wq_id').value =  Element.collectTextNodes(nodes[0], 'wq_id');
           } "} %>
<input id="wq_id" name="add[wq_id]" type="hidden" value=""/>

You could also go further and update other parts of the page or have other fields, but you should get the idea for now.