Two text_field_with_auto_complete field with the same search content

10 views
Skip to first unread message

Todd Chambery

unread,
Jun 13, 2008, 2:27:41 PM6/13/08
to Ruby on Rails: Talk
Hi all,

I have a form with two text_field_with_auto_complete s, both of which
do a lookup from the same table:

<!-- good guys --><td><%=
text_field_with_auto_complete :person, :name %></td>
<!-- bad guys --><td><%=
text_field_with_auto_complete :person, :name %></td>

Unsurprisingly, this produces identical output for both:

<input id="person_name" name="person[name]" size="30"
type="text" />

Is there a way to set the id of the field to something other than the
default?

Thanks,

Todd

julian

unread,
Jun 13, 2008, 3:12:39 PM6/13/08
to Ruby on Rails: Talk
text_field_with_auto_complete(object, method, tag_options = {},
completion_options = {})

Well, you can change the id with the tag_options hash, but the
generated javascript won't know about it. So, let's improve
text_field_with_auto_complete so that it does work.

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

The content_tag and the auto_complete_field methods both set the ids
of elements without consulting what id you passed to the tag_options
hash. We should change those two calls to match whatever :id option
was passed in, if in case it was passed in.

So...

def text_field_with_auto_complete_with_id_checking(object, method,
tag_options = {}, completion_options = {})
# lets just set the id now and not worry about it throughout
id = tag_options[:id] || "#{object}_#{method}"

(completion_options[:skip_style] ? "" : auto_complete_stylesheet) +
text_field(object, method, tag_options) +
content_tag("div", "", :id => "#{id}_auto_complete", :class =>
"auto_complete") +
auto_complete_field(id,
{ :url => { :action =>
"auto_complete_for_#{object}_#{method}" } }.update(completion_options))
end

To get this to work just place it in your application_helper.rb (and
call it with
text_field_with_auto_complete_with_id_checking(:person, :name, {:id =>
'bad_person'})

This seems to work for me but it doesn't have any tests written for it
as I just threw it together... I'll leave that to someone else.

Julian

Todd Chambery

unread,
Jun 14, 2008, 10:20:22 AM6/14/08
to Ruby on Rails: Talk
Brilliant! That works a treat.

So a follow on question:

How do I get access to the completion_options in my view? I need to
add some conditions to the search in my person controller ("filter on
org")?

view:
<!-- good guys --><td><%=
text_field_with_auto_complete :person, :name, { :id => 'goodguy' }
{ :org => "justice_league" } %></td>
<!-- bad guys --><td><%=
text_field_with_auto_complete :person, :name, { :id => 'badguy' }
{ :org => "legion_of_doom" } %></td>

controller:
def auto_complete_for_person_name
...
find_options = { :conditions => "org='" + ?????? +
"'", :order => "name ASC" }
@entities = Person.find(:all,
find_options).collect(&:name).select { |person| person.match re }


As an aside: coming from Java (and Eclipse), the hardest part of
picking up Rails is the lack of a "discoverable API". If you have any
pointers (books, links, etc), I'd be much obliged.

Thanks again,

Todd

julian

unread,
Jun 14, 2008, 4:09:44 PM6/14/08
to Ruby on Rails: Talk
One of the completion_options is :with. If you don't use the :with
option the default parameter available to the action is the value of
the text field. However, once you do use the :with option you have to
specify all the parameters available to the action including the one
that was sent by default (the "'person[name]=' + $('goodguy').value"
part).

text_field_with_auto_complete :person, :name, { :id => 'goodguy' }
{ :with => "'person[name]=' + $('goodguy').value +
'&person[org]=justice_league'"}

What's a "discoverable API"?

I just use api.rubyonrails.org

Todd Chambery

unread,
Jun 14, 2008, 5:03:16 PM6/14/08
to Ruby on Rails: Talk


On Jun 14, 4:09 pm, julian <thefool...@gmail.com> wrote:
> One of the completion_options is :with.  If you don't use the :with
> option the default parameter available to the action is the value of
> the text field.  However, once you do use the :with option you have to
> specify all the parameters available to the action including the one
> that was sent by default (the "'person[name]=' + $('goodguy').value"
> part).
>
> text_field_with_auto_complete :person, :name, { :id => 'goodguy' }
> { :with => "'person[name]=' + $('goodguy').value +
> '&person[org]=justice_league'"}

Thanks again! Once again, this did the trick.

> What's a "discoverable API"?
>
> I just use api.rubyonrails.org

Not to send this thread spinning off into another universe, but: it
seems that in a number of Rails methods, one or more arguments is a
Hash. No problem with it in principle, but it's not transparent
("discoverable") from the signature what values will do what. For
comparison, with Java + Eclipse, it's clear what data should be sent
to the method.

I'm quite enamored with Rails, but this point is the hardest
adjustment coming from a statically-typed language.

Todd

Xavier Noria

unread,
Jun 14, 2008, 6:32:37 PM6/14/08
to rubyonra...@googlegroups.com
On Sat, Jun 14, 2008 at 11:03 PM, Todd Chambery <todd.c...@gmail.com> wrote:

> Not to send this thread spinning off into another universe, but: it
> seems that in a number of Rails methods, one or more arguments is a
> Hash. No problem with it in principle, but it's not transparent
> ("discoverable") from the signature what values will do what. For
> comparison, with Java + Eclipse, it's clear what data should be sent
> to the method.
>
> I'm quite enamored with Rails, but this point is the hardest
> adjustment coming from a statically-typed language.

It's quite easy once you understand how it works.

As syntactic sugar Ruby builds a single hash from any trailing
hash-like arguments and passes *it* as the corresponding positional
argument. For example in

def foo(x, y=0)
...
end

the call

foo(:a => 1, :b => 2, :c => 3)

is equivalent to

foo({:a => 1, :b => 2, :c => 3})

and both of them result in

x = {:a => 1, :b => 2, :c => 3}
y = 0

In that situation, how can you pass a -7 for y? You have to make
explicit the 1st argument:

foo({:a => 1, :b => 2, :c => 3}, -7)

That's the basic stuff. Other than that what you need to do with those
helpers, as with any other API, is to read its documentation. In
particular their signature.

Todd Chambery

unread,
Jun 16, 2008, 8:55:00 AM6/16/08
to Ruby on Rails: Talk
After some further tinkering, I'm able to get 'goodguy' and 'badguy'
values passed to the controller (the 'method per id' is far from
ideal....):

entry.rhtml

<p>
<label for="attr_badguy_name">Bad Guy:</label>
<%=
text_field_with_auto_complete_with_id_checking :entity, :name, { :id
=> "badguy" }, { :with => "'badguy[name]=' + $('badguy').value +
'&entity[schema_type]=complex'"} %>
</p>
<p>
<label for="attr_type_name">Good Guy:</label>
<%=
text_field_with_auto_complete_with_id_checking :goodguy, :name, { :id
=> "goodguy" }, { :with => "'goodguy[name]=' + $('goodguy').value" }
%>
</p>

--------------------

module ApplicationHelper

def text_field_with_auto_complete_with_id_checking(object, method,
tag_options = {},
completion_options = {})
# lets just set the id now and not worry about it throughout
id = tag_options[:id] || "#{object}_#{method}"
print "\n\n\n\t\t"
pp id

(completion_options[:skip_style] ? "" : auto_complete_stylesheet)
+
text_field(id, method, tag_options) +
content_tag("div", "", :id => "#{id}_auto_complete", :class
=>"auto_complete") +
auto_complete_field(id, { :url => { :action =>
"auto_complete_for_#{id}_#{method}" } }.update(completion_options))
end

--------------------

class AttrController < ApplicationController

protect_from_forgery :only => [:create, :update, :destroy]

def auto_complete_for_goodguy_name
print "\n\n\t\tin auto_complete_for_goodguy_name\n"
pp params
org = params[:person][:org].nil? ? "" : "org='" + params[:person]
[:org] + "'"
re = Regexp.new("^#{params[:goodguy][:name]}", "i")
find_options = { :conditions => org, :order => "name ASC" }
@people = Person.find(:all, find_options).collect(&:name).select
{ |person| person.match re }

render :inline => "<%= content_tag(:ul, @people.map { |person|
content_tag(:li, h(person)) }) %>"
end

def auto_complete_for_badguy_name
print "\n\n\t\tin auto_complete_for_badguy_name\n"
pp params
org = params[:person][:org].nil? ? "" : "org='" + params[:person]
[:org] + "'"
re = Regexp.new("^#{params[:badguy][:name]}", "i")
find_options = { :conditions => org, :order => "name ASC" }
@people = Person.find(:all, find_options).collect(&:name).select
{ |person| person.match re }

render :inline => "<%= content_tag(:ul, @people.map { |person|
content_tag(:li, h(person)) }) %>"
end



On Jun 14, 4:09 pm, julian <thefool...@gmail.com> wrote:

Frederick Cheung

unread,
Jun 16, 2008, 9:02:21 AM6/16/08
to rubyonra...@googlegroups.com

On 16 Jun 2008, at 13:55, Todd Chambery wrote:

>
> After some further tinkering, I'm able to get 'goodguy' and 'badguy'
> values passed to the controller (the 'method per id' is far from
> ideal....):
>

don't forget to use encodeURIComponent or bad things will happen if
you type & into the box.

Fred

julian

unread,
Jun 16, 2008, 9:49:53 AM6/16/08
to Ruby on Rails: Talk
A couple of suggestions:

First, you'll be better of using the database to search through the
names for matches as that should be faster then pulling all the names
and running them through a regular expression. You can use
a :conditions option to the ActiveRecord::Base.find method like this
(works with mysql, though I think it's standard SQL) :conditions =>
["name LIKE ?", "#{name}%"]. The LIKE will match case insensitive and
allows the wildcard character (the percent sign) and the placeholder
substitution will prevent sql injection attacks.

Second, I think the point of my hack to text_field_with_auto_complete
was to allow the controller to have one method. So you could have
just auto_complete_for_person_name where it's something like this.

def auto_complete_for_person_name
@people = Person.find(:all,
:conditions => ["name LIKE ? AND org = ?", "#{params[:person]
[:name]}%", params[:person][:org]],
:order => "name ASC")
unless @people.blank?
render :inline => "<%= content_tag(:ul,
@people.map{|person|
content_tag(:li, h(person)) }) %>"
else
render :inline => ""
end
end

Assuming you always pass the [person][org] parameter, which you don't
seem to be doing anymore.

Also, Frederick's point is very important! (My mistake)

So the final text_field_with_auto_complete_with_id_checking call
should look like this:

text_field_with_auto_complete_with_id_checking :person, :name, {:id =>
"badguy"},
{:with => "'person[name]=' + encodeURIComponent($('badguy').value) +
'&person[org]=legion_of_doom'"}

Finally, in your controller you will want to make sure that @people is
not blank before sending it to map to avoid:

NoMethodError: undefined method `map' for nil:NilClass

so:

unless @people.blank?
render ...
else
render :inline => ""
end

If you do need two different controller methods for some other reason
then you should still take the similar stuff and stick it in a helper
or private method.

Julian

On Jun 16, 9:02 am, Frederick Cheung <frederick.che...@gmail.com>
wrote:

Todd Chambery

unread,
Jun 16, 2008, 11:18:47 AM6/16/08
to Ruby on Rails: Talk
Julian,

Your solution works for providing and filtering the completion
suggestions. The problem I'm running into is what params get sent on
submit:

Parameters: {"commit"=>"Add People", "person"=>{"name"=>"Superman"},
"authenticity_token"=>"033d880da069739bd1b80f42e11c6008e
54971b9", "action"=>"save_matchup", "controller"=>"matchup",
"matchup"=>{"name"=>"foo", "description"=>"sdafasdf"}}

In this case, "Superman" is the value in the first text field. The
generated HTML reveals that both text auto-complete boxes have the
same name:

<input autocomplete="off" id="goodguy" name="person[name]" ...

<input autocomplete="off" id="badguy" name="person[name]" ...

It looks like I need a unique "name" attribute as well as a unique id
(if I actually need a unique id at all).

Todd

julian

unread,
Jun 16, 2008, 12:08:51 PM6/16/08
to Ruby on Rails: Talk
Well, now that you've brought up the submit part... yeah, the names
would have to be different too. So let's just take my
text_field_with_auto_complete hack and crumple it up and throw it in
the trash (or maybe hold on to it for another day).

Let's go back to using the straight up text_field_with_auto_complete
but move common stuff into the model.

Model:

def self.search_by_name(name, org = nil)
conditions =
if org
["name LIKE ? AND org = ?",
"#{name}%",
"#{org}"]
else
["name LIKE ?", "#{name}%"]
end
Person.find(:conditions => conditions,
:order => "name ASC")
end

Controller:

def auto_complete_for_goodguy_name
people = Person.search_by_name(
params[:goodguy][:name], "justice_league")
render :partial => 'auto_complete_results',
:locals => {:people => people}
end

def auto_complete_for_badguy_name
people = Person.search_by_name(
params[:goodguy][:name], "legion_of_doom")
render :partial => 'auto_complete_results',
:locals => {:people => people}
end

View:

the form:

<!-- good guys --><%= text_field_with_auto_complete :goodguy, :name %>
<!-- bad guys --><%= text_field_with_auto_complete :badguy, :name %>

And a partial _auto_complete_results.html.erb:

<% unless people.blank? -%>
<%= content_tag(:ul,
people.map{|person| content_tag(:li, h(person))} ) %>
<% end -%>

Or something like that.

julian

unread,
Jun 16, 2008, 12:11:52 PM6/16/08
to Ruby on Rails: Talk
As you can tell, I haven't really tested any of this code I've been
tossing up here... :(

the Person.find call in the search_by_name method should be

Person.find(:all, :conditions => conditions,
:order => "name ASC")

Todd Chambery

unread,
Jun 16, 2008, 1:29:14 PM6/16/08
to Ruby on Rails: Talk
I think I may have cracked it.

In the view, I explicitly set the id in the :with clause.
In the person_controller, I get the value of the text field by
grabbing the 'id' name out of the parameters, and using the value of
'id' as key to get the value of the textbox.
In the application_helper, I pass the value of tag_options[:id] to the
text_field generator instead of :object.

Thanks for your help, hopefully this will help the next guy :).

Todd

view:
<%=
text_field_with_auto_complete_with_id_checking :person, :name, { :id
=> "badguy" }, { :with => "'id=badguy&badguy[name]=' +
encodeURIComponent($('badguy').value) +
'&person[org]=legionofdoom"} %>

person_controller:
def auto_complete_for_person_name
id = params[:id]
@people = person.find(:all,
:conditions => ["name LIKE ? AND org=?",
"#{params[id][:name]}%", "#{params[person][:org]}%"],
:order => "name ASC")
unless @people.blank?
render :inline => "<%= content_tag(:ul, @people.map{|person|
content_tag(:li, h(person)) }) %>"
else
render :inline => ""
end
end

application_helper:
def text_field_with_auto_complete_with_id_checking(object, method,
tag_options = {},
completion_options = {})
# lets just set the id now and not worry about it throughout
id = tag_options[:id] || "#{object}_#{method}"

(completion_options[:skip_style] ? "" : auto_complete_stylesheet)
+
text_field(id, method, tag_options) +
content_tag("div", "", :id => "#{id}_auto_complete", :class =>
"auto_complete") +
auto_complete_field(id,
{ :url => { :action =>
"auto_complete_for_#{object}_#{method}" } }.update(completion_options))
end




Shandy Nantz

unread,
Oct 26, 2009, 2:43:29 PM10/26/09
to rubyonra...@googlegroups.com

Hi all,

Has anybody done anything with this code? I am trying to use it and I am
not sure if it works. I have made a few changes but seem to be crawling
deeper and deeper into a bottomless hole. My issue is that I have
several divs with the same auto_completer for each and all within
different forms and all located on the same page. The issue became that
when I used the auto_completer on a div located lower on the page it
appeared not to work, when in fact it did work, it was just populating
the list higher up on the page (because the id fields were named the
same), which is why I needed to re-name each auto_completer with a
unique name. My code looks like:

View
-----
<div id = "clone_to_div">
Clone to Pseudocity: <%=
text_field_with_auto_complete_with_id_checking
:enpseudo, :pseudocity, {:id => 'enpseudo_service_fees'},
{:skip_style => true} %>
</div>

<%= link_to_remote( '<span>Clone</span>',
{:submit => 'clone_to_div', :update => 'clone_fee_div',
:complete => 'new Effect.Pulsate(\'clone_fee_div\');',
:url => {:action => 'clone_pseudo_fee', :pseudo_id => @pseudo.id}
}, :class => 'squarebutton') %>

The issue that I am having is that when the form is submitted nothing
from the auto_complete is submitted. One issue that I have noticed from
the HTML generated code is that the <input ... /> has the correct id but
an incorrect name. So, I did as suggested and gave it a name via the
tag_options and that's when things started to go south. By supplying a
name the auto_complete_field seems to stop working all together. Remove
it and it works but nothing gets passed along in my post - this seems to
be a catch-22.

Any suggestions on what I am doing wrong or can someone point me in the
right direction? Thanks for any and all help,

-S
--
Posted via http://www.ruby-forum.com/.

Shandy Nantz

unread,
Oct 26, 2009, 4:42:44 PM10/26/09
to rubyonra...@googlegroups.com
Shandy Nantz wrote:
> View
> -----
> <div id = "clone_to_div">
> Clone to Pseudocity: <%=
> text_field_with_auto_complete_with_id_checking
> :enpseudo, :pseudocity, {:id => 'enpseudo_service_fees'},
> {:skip_style => true} %>
> </div>
>
> <%= link_to_remote( '<span>Clone</span>',
> {:submit => 'clone_to_div', :update => 'clone_fee_div',
> :complete => 'new Effect.Pulsate(\'clone_fee_div\');',
> :url => {:action => 'clone_pseudo_fee', :pseudo_id => @pseudo.id}
> }, :class => 'squarebutton') %>
>

Never mind, my fault. I had multiple "clone_to_div" that was causing the
issue. Once I remained these div to have unique id's all issues went
away.

Reply all
Reply to author
Forward
0 new messages