Update June 2009: I just added support to my version of auto_complete to support Rails 2.3 nested attributes; for more details see: http://patshaughnessy.net/repeated_auto_complete. The basic ideas below still apply, but my implementation of auto_complete has changed, and I’ve also simplified the usage.
In October I described how the auto_complete plugin doesn’t work when text fields are repeated more than once on a complex form. I went on to write a plugin called “repeated_auto_complete” which modified the way the standard auto_complete plugin works and fixed this problem by adding random numbers to <input id=""> attributes among other changes.
Since it’s much cleaner to have a single auto_complete plugin rather than two separate plugins, I’ve merged my changes to auto_complete into the original version, and pushed them to github as a new fork: http://github.com/patshaughnessy/auto_complete
To install and use my modified version of auto_complete first remove the standard auto_complete plugin from your app if necessary, and install with:
script/plugin install git://github.com/patshaughnessy/auto_complete.git
To use auto complete in a complex form, you write “auto_complete_fields_for” or “auto_complete_form_for” in your view, and then call text_field_with_auto_complete on the form builder object, as follows:
<% for person in @group.people %>
<% auto_complete_fields_for "group[person_attributes][]", person do |form| %>
Person <%= person_form.label :name %><br />
<%= form.text_field_with_auto_complete :person, :name, {},
{:method => :get} %>
<% end %>
<% end %>
To understand my changes to the plugin, let’s first look at how the original auto_complete works. If you add this line to your view:
<%= text_field_with_auto_complete :project, :name, {}, {:method => :get } %>
…then you get HTML and script that looks like this (style sheet omitted):
<input id="project_name" name="project[name]" size="30" type="text" />
<div class="auto_complete" id="project_name_auto_complete"></div>
<script type="text/javascript">
//<![CDATA[
var project_name_auto_completer = new Ajax.Autocompleter('project_name',
'project_name_auto_complete', '/projects/auto_complete_for_project_name',
{method:'get'})
//]]>
</script>
The original text_field_with_auto_complete method looked like this:
def text_field_with_auto_complete(object, method, tag_options = {},
completion_options = {})
(completion_options[:skip_style] ? "" : auto_complete_stylesheet) +
text_field(object, method, tag_options) +
content_tag("div", "", :id => "#{object}_#{method}_auto_complete",
:class => "auto_complete") +
auto_complete_field(
"#{object}_#{method}",
{
:url => { :action => "auto_complete_for_#{object}_#{method}" }
}.update(completion_options))
end
You can see that it calls “text_field” in ActionView::Helpers::FormHelper to generate the actual <input> tag for the form, in addition to generating the HTML and script needed for the auto completion behavior.
What I wanted to achieve in the modified plugin was to allow the view to contain code like this:
<% auto_complete_fields_for task do |f| %>
<%= f.label :name, "Task:" %>
<%= f.text_field_with_auto_complete :task, :name, {}, {:method => :get } %>
<% end %>
To make this work, we need a new version of text_field_with_auto_complete that calls text_field from ActionView::Helpers::FormBuilder, and not ActionView::Helpers::FormHelper, generating an <input> tag similar to what this call would generate:
<% fields_for task do |f| %> <%= f.text_field :name %> <% end %>
To do this, I first refactored the original text_field_with_auto_complete in auto_complete_macros_helper.rb:
def text_field_with_auto_complete(object, method, tag_options = {},
completion_options = {})
auto_complete_field_with_style_and_script(object, method, tag_options,
completion_options) do
text_field(object, method, tag_options)
end
end
def auto_complete_field_with_style_and_script(object, method,
tag_options = {},
completion_options = {})
(completion_options[:skip_style] ? "" : auto_complete_stylesheet) +
yield +
content_tag("div", "", :id => "#{object}_#{method}_auto_complete",
:class => "auto_complete") +
auto_complete_field(
"#{object}_#{method}",
{
:url => { :action => "auto_complete_for_#{object}_#{method}" }
}.update(completion_options))
end
Here I’ve introduced a new utility function called “auto_complete_field_with_style_and_script” that generates the same Javascript and style sheet for the view as before, but instead calls a block to generate the actual text field. Then I changed text_field_with_auto_complete to call this, providing a block to make the call to “text_field” in ActionView::Helpers::FormHelper with the proper names and options.
Now my new form builder class in auto_complete_form_helper.rb contains a version of text_field_with_auto_complete that looks like this:
def text_field_with_auto_complete(object,
method,
tag_options = {},
completion_options = {})
unique_object_name = "#{object}_#{Object.new.object_id.abs}"
completion_options_for_original_name =
{
:url => { :action => "auto_complete_for_#{object}_#{method}"},
:param_name => "#{object}[#{method}]"
}.update(completion_options)
@template.auto_complete_field_with_style_and_script(
unique_object_name,
method,
tag_options,
completion_options_for_original_name
) do
text_field(method,
{
:id => "#{unique_object_name}_#{method}"
}.update(tag_options))
end
end
Here the call to auto_complete_field_with_style_and_script passes a block that calls the other text_field from ActionView::Helpers::FormBuilder (note the “object” parameter is not present as above).
To allow the text field to be repeated on a complex form, I insure the object’s name is unique by adding a random number to it (“unique_object_name”). This unique name is then passed into both auto_complete_field_with_style_and_script and text_field, insuring that the <input> and related Javascript all work without problems, even if the text field is repeated more than once on the same form.
The last important detail here is that the completion options passed into auto_complete_field_with_style_and_script are generated using the original, unchanged (non-unque) object name, so that the Ajax calls to the server are made using the original name. This means no changes are required on the server side, and the same single line of code in your controller still works as usual:
auto_complete_for :task, :name
Next time I’ll post a sample application that uses this new plugin, and explain what changes you will need to make to your own application for auto_complete in a complex form.
7 responses so far ↓
1 Matt // Feb 22, 2009 at 11:55 AM
2 pat // Feb 22, 2009 at 07:51 PM
Thanks so much for trying my plugin Matt… it seems to me that the key to your problem is the random number my code is inserting into the HTML/Javascript object names generated by text_field_with_auto_complete. If you had the ability to specify this value (you would have to insure it was unique) then you could write Javascript in your form as desired to set different values. For example, you could set the “random number” instead to be the index/counter value from your loop generating the repeated fields…
I’m imagining a new tag option called “:unique_object_id => 1234”. Then in my code instead of unique_object_name = "#{object}_#{Object.new.object_id.abs}" it would use “#{object}_#{tag_options[:unique_object_id]” if this value were present. Just thinking aloud here… would this work for you? If so, I might have time later this week to code it up and post it on github. Or feel free to try it yourself and let me know.
3 Matt // Feb 22, 2009 at 08:54 PM
4 Nico // Jan 06, 2010 at 05:24 AM
5 Tyler Gannon // Mar 19, 2010 at 02:10 AM
6 pat // Mar 19, 2010 at 05:33 PM
7 pat // Mar 24, 2010 at 02:17 PM
Hey Tyler – so I did take some time to setup Rails 3 on my machine and try it out. You’re right; repeated_auto_complete didn’t work immediately because for some reason (still need to investigate this) Rails 3 didn’t call the rails/init.rb file in the repeated_auto_complete gem. I was able to get it to work by installing it as a plugin instead. For example, here’s how to setup and use repeated_auto_complete in a new Rails 3 app:
$ rails -v Rails 3.0.0.beta $ rails auto_complete_test create create README create .gitignore create Rakefile create config.ru create Gemfile create app etc... $ rails plugin install git://github.com/patshaughnessy/auto_complete.gitCreate a person scaffold:
$ rails generate scaffold person name:string invoke active_record create db/migrate/20100324171356_create_people.rb create app/models/person.rb invoke test_unit create test/unit/person_test.rb create test/fixtures/people.yml route resources :people invoke scaffold_controller create app/controllers/people_controller.rb etc... $ rake db:migrate (in /Users/pat/rails-apps/rails3/auto_complete_test) == CreatePeople: migrating ======================== -- create_table(:people) -> 0.0012s == CreatePeople: migrated (0.0013s) ===============Edit config/routes.rb and add the match line:
Edit people_controller.rb:
Finally, use auto complete in your view (app/views/people/_form.html.erb):
Leave a Comment