Module ModelAutoCompleterHelper
In: lib/model_auto_completer_helper.rb

Methods

Public Instance methods

Generates a text field that autocompletes a belongs_to association, and a hidden field managed with JavaScript that stores the ID of selected models.

Say we have these models:

  class Author < ActiveRecord::Base
    has_many :books
  end

  class Book < ActiveRecord::Base
    belongs_to :author
  end

In the form to edit books you can just do this to assign an author by autocompletion on her name:

  <%= belongs_to_auto_completer :book, :author, :name %>

We assume here BooksController implements an action called auto_complete_belongs_to_for_book_author_name:

  def auto_complete_belongs_to_for_book_author_name
    @authors = Author.find(
      :all,
      :conditions => ['LOWER(name) LIKE ?', "%#{params[:author][:name]}%"],
      :limit => 10
    )
    render :inline => '<%= model_auto_completer_result(@authors, :name) %>'
  end

though that can be configured, see options below.

There is convenience class method for controllers auto_complete_belongs_to_for which generates a default action, analogous to the one in the standard autocompleter.

The text field is named "association[method]", in the example "author[name]". We don‘t include the object so that params[:book] does not contain that auxiliary value.

The hidden field is named "object[association_foreign_key]", in the example that is "book[author_id]". The goal is that regular mass-assignement idioms like Book.new(params[:book]) work as usual and are all you need to associate the author. The name of the foreign key is figured out dynamically by reflection on the association.

See the documentation of model_auto_completer for further details and options. This helper is just a convenience wrapper for that one.

[Source]

    # File lib/model_auto_completer_helper.rb, line 52
52:   def belongs_to_auto_completer(object, association, method, options={}, tag_options={}, completion_options={})
53:     real_object  = instance_variable_get("@#{object}")
54:     foreign_key  = real_object.class.reflect_on_association(association).primary_key_name
55: 
56:     tf_name  = "#{association}[#{method}]"
57:     tf_value = (real_object.send(association).send(method) rescue nil)
58:     hf_name  = "#{object}[#{foreign_key}]"
59:     hf_value = (real_object.send(foreign_key) rescue nil)
60:     options  = {
61:       :action => "auto_complete_belongs_to_for_#{object}_#{association}_#{method}"
62:     }.merge(options)
63:     model_auto_completer(tf_name, tf_value, hf_name, hf_value, options, tag_options, completion_options)
64:   end

This is the most generic helper for model autocompletion. This widget creates a text field and manages a hidden field where the ID of the selected model is stored.

Autocompletion itself is delegated to the standard Rails autocompleter. You can pass options for it in the rightmost argument. For example, to disable inline CSS pass :skip_style => true.

By default, the name of the action to invoke is auto_complete_model_for_ and a suffix computed from the text field name (tf_name). If the text field is called "owner[fullname]" we obtain auto_complete_model_for_owner_fullname, you see how it works. The text field initially contains tf_value.

Note that model_auto_completer itself uses the underlying callback :after_update_element to extract the model and do some housekeeping. If you need a callback use the provided wrapper instead, which in addition receives the hidden field and the extracted model id. See options below.

The hidden field will be named hf_name and will have an initial value of hf_value.

Generated INPUT elements have a random suffix in their ids so that you can include this widget more than once in the same page with negligible risk of collision. You can turn this off via :append_random_suffix.

The widget expects a regular unordered list of completions as you send for the standard Rails autocompleter, except list items are required to have an id attribute.

By default, any trailing integers in the id attributes will be considered to be the identifiers of the corresponding models. There‘s a configurable regexp to extract them though, see options below.

Normally you are done sending the completion list with something like

  render :inline => '<%= model_auto_completer_result(@authors, :name) %>'

But the actual contract is to send back a HTML list, where the content of the items may have arbitrary stuff:

  <ul>
    <% for author in @authors %>
    <li id="<%= dom_id(author) %>">
      <%= avatar(author) %> <%=h author.name %>
    </li>
    <% end %>
  </ul>

the helper model_auto_completer_result generates something like that.

Available options are:

  • :regexp_for_id: A regexp with at least one group. The first capture is assumed to be the ID of the corresponding model. Defaults to (\d+)$.
  • :allow_free_text: If false the widget only allows values that come from autocompletion. If the user leaves the text field with a free string the text field is rolled back to the last valid value. If true free edition is allowed, and if the text field contains free text the hidden field will contain the empty string. Defauts to false.
  • :append_random_suffix: If true the HTML id of the generated fields gets a random suffix to avoid collisions in case you put the widget more than once in the same page. Defaults to true. (Since 1.5.)
  • :submit_on_return: Some browsers submit the form if you select and item from the completion list with the keyboard. If this flag is off the return key is captured and discarded. Defaults to false. (Since 1.5.)
  • :send_on_return: Deprecated. Alias for :submit_on_return that is available for backwards compatibility.
  • :after_update_element: A JavaScript function that is called when the user has selected one of the completions. It gets four arguments, the text field, the selected list item, the hidden field, and the extracted model id.
  • :url: The URL that provides completions. Use this for named routes. If this option has a value :controller and :action are just ignored. (Since 1.5.)
  • :controller: The controller that implements the action that returns completions. Defaults to the current controller.
  • :action: The action that provides the completions. The default is explained above.

[Source]

     # File lib/model_auto_completer_helper.rb, line 174
174:   def model_auto_completer(tf_name, tf_value, hf_name, hf_value, options={}, tag_options={}, completion_options={})
175:     options = {
176:       :regexp_for_id        => '(\d+)$',
177:       :append_random_suffix => true,
178:       :allow_free_text      => false,
179:       :submit_on_return     => false,
180:       :controller           => controller.controller_name,
181:       :action               => 'auto_complete_model_for_' + tf_name.sub(/\[/, '_').gsub(/\[\]/, '_').gsub(/\[?\]$/, ''),
182:       :after_update_element => 'Prototype.emptyFunction'
183:     }.merge(options)
184:     options[:submit_on_return] = options[:send_on_return] if options[:send_on_return]
185: 
186:     hf_id, tf_id = determine_field_ids(options)
187:     determine_tag_options(hf_id, tf_id, options, tag_options)
188:     determine_completion_options(hf_id, options, completion_options)
189: 
190:     return "\#{auto_complete_stylesheet unless completion_options[:skip_style]}\n\#{hidden_field_tag(hf_name, hf_value, :id => hf_id)}\n\#{text_field_tag tf_name, tf_value, tag_options}\n\#{content_tag(\"div\", \"\", :id => \"\#{tf_id}_auto_complete\", :class => \"auto_complete\")}\n\#{auto_complete_field tf_id, completion_options}\n"
191:   end

Returns an unordered HTML list of completion results that is understood by the client code right away. This is meant to be used by controllers this way:

  render :inline => '<%= model_auto_completer_result(@users, :fullname) %>'

The string shown per model is the result of invoking display on them.

If you pass a phrase it will be highlighted in each entry.

[Source]

    # File lib/model_auto_completer_helper.rb, line 75
75:   def model_auto_completer_result(models, display, phrase=nil)
76:     # We can't assume dom_id(model) is available because the plugin does not require Rails 2 by now.
77:     prefix = models.first.class.name.underscore.tr('/', '_') unless models.empty?
78:     items = models.map do |model|
79:       li_id      = "#{prefix}_#{model.id}"
80:       li_content = model.send(display)
81:       content_tag('li', (phrase ? highlight(li_content, phrase) : h(li_content)), :id => li_id)
82:     end
83:     content_tag('ul', items.uniq)
84:   end

[Validate]