Dynamic dropdowns using just Ruby

Hi all,

I am trying to create dynamic dropdowns in a form, such that when a user
clicks an item in select list #1 (States), the options in select list #2
(Cities) will update to reflect only cities that are in the selected
state.

I know this can be done with Javascript, but I came across a little
snippet online (see <a
href="http://nealenssle.com/blog/2007/04/12/how-to-dynamically-update-form-elements-in-rails-using-ajax/"
target="_blank">this blog by Neal Enssle</a>) that does it all in Rails
and that would be amazing if I could get it to work because I'm really
not big on JS.

I can't get this code to work. When I select a state in the first
dropdown, the 2nd dropdown does not populate.

     <%= observe_field "states", :update => "cities", :with =>
"city_id", :url => { :controller => "test", :action => "get_cities" } %>

Firstly observe_field is a helper method that generates JavaScript. Do
you have JavaScript enabled in your browser? If not then observe_field
will not work.

Secondly, the web is stateless. Choosing an item in a drop-down list
does not send anything back to the server (where your Ruby code
lives). Browsers don't run Ruby code. They run JavaScript.

Finally, my suggestion would be to get over not being "big on
JavaScript." Just suck it up like the rest of us and use it.

Some of the most interesting things happening on the web right now are
happening in JavaScript. This is Web 2.0 after all. Rails provides
ways to generate JavaScript by using some meta-programming tricks, and
is called Ruby JavaScript (RJS). But, at the end of the day it's still
JavaScript running in the browser.

Sara Me wrote:

Oh, by the way here is a small snippet from the Rail API docs on
observe_field:

# Generates: new Form.Element.Observer('suggest', 0.25,
function(element, value) {new Ajax.Updater('suggest',
# '/testing/find_suggestion', {asynchronous:true,
evalScripts:true, parameters:'q=' + value})})
<%= observe_field :suggest, :url => { :action => :find_suggestion },
      :frequency => 0.25,
      :update => :suggest,
      :with => 'q'
      %>

Notice that the comments show the JavaScript generated by the helper
method. In the JavaScript you will also notice the Ajax.Update object.
This is going to require that your page links to the Prototype
JavaScript framework so your page header will need:

<%= javascript_include_tag :defaults %>

Hello there,

Guess I should clarify a little bit. I do use JavaScript, but in order
to do what I want to do with the dropdowns, the more I can pare it down,
the better. See more below:

Robert Walker wrote:>

Firstly observe_field is a helper method that generates JavaScript. Do
you have JavaScript enabled in your browser? If not then observe_field
will not work.

--- Yes, I have JS enabled in my browser(s).

Secondly, the web is stateless. Choosing an item in a drop-down list
does not send anything back to the server (where your Ruby code
lives). Browsers don't run Ruby code. They run JavaScript.

--- I understand the need for JS here. I do have the Prototype scripts
loaded into my application layout using <%= javascript_include_tag
:defaults %> in the Head section. The script is being loaded into the
page, as I can see from looking at the page source.

Ironically I am trying to learn more about JavaScript and AJAX via the
functionality provided by Rails. I aim to be much better at JavaScript,
which is why I've been sitting here struggling with this one stupid
issue for hours.

What I would really like to know at the moment is why all of this is
still not working, and where I might be going wrong in my code.

Hi all,

I am trying to create dynamic dropdowns in a form, such that when a user
clicks an item in select list #1 (States), the options in select list #2
(Cities) will update to reflect only cities that are in the selected
state.

I know this can be done with Javascript, but I came across a little
snippet online (see <a
href="http://nealenssle.com/blog/2007/04/12/how-to-dynamically-update-form-elements-in-rails-using-ajax/"
target="_blank">this blog by Neal Enssle</a>) that does it all in Rails
and that would be amazing if I could get it to work because I'm really
not big on JS.

I can't get this code to work. When I select a state in the first
dropdown, the 2nd dropdown does not populate.

*************
Here is the code in controllers/dyn_drops_controller.rb:

     def get_cities
          @cities = City.find_by_state(:all)
     end

*************

Here is the code in views/dyn_drops/index.html.erb:

     <form>

       <select id="states" name="states">
           <option value="0"></option>
           <option value="1">New York</option>
           <option value="2">California</option>
           <option value="3">British Columbia</option>
           <option value="4">Ontario</option>
       </select>

     <%= observe_field "states", :update => "cities", :with =>
"city_id", :url => { :controller => "test", :action => "get_cities" } %>

       <br />

       <select id="cities" name="cities">
           <option value="0"></option>
           <option value=1">test option</option>
       </select>

     </form>

*************

Here is the code in views/dyn_drops/get_cities.rhtml:

     <% for city in @cities -%>
         <option value="<%= city.id %>"><%= city.name %></option>
     <% end -%>

*************

I also have a model named city.rb. One MySQL db table is named 'states'
and contains fields 'id' and 'name'. The other db table is named
'cities' and contains fields for 'id', 'name', and 'state_id'.

I am a newbie to RoR so I'm sure the problem here is something really
dumb I'm missing. I know there's a lot of info above, so any help would
be greatly appreciated!!

OK, I am getting closer!

Per Craig, I changed the following line:
   :url => { :controller => "test", :action => "get_cities" } %>

to:
   :url => { :controller => "dyn_drops", :action => "get_cities" } %>

but I was still having no luck. I then went into the controller and
changed the following line:

          @cities = City.find_by_state(:all)

to:
          @cities = City.find_all_by_state_id(1)

At this point, with a specific state_id given, the 2nd dropdown will
populate with only the cities for the named state_id. If I could just
get it to pass in the correct state_id from the 1st dropdown. This
stinks, I feel like I am so close!!

Craig White wrote:

Hi

Hi

> OK, I am getting closer!
>
> Per Craig, I changed the following line:
> :url => { :controller => "test", :action => "get_cities" } %>
>
> to:
> :url => { :controller => "dyn_drops", :action => "get_cities" } %>
>
> but I was still having no luck. I then went into the controller and
> changed the following line:
>
> @cities = City.find_by_state(:all)
>
> to:
> @cities = City.find_all_by_state_id(1)
>
> At this point, with a specific state_id given, the 2nd dropdown will
> populate with only the cities for the named state_id. If I could just
> get it to pass in the correct state_id from the 1st dropdown. This
> stinks, I feel like I am so close!!
>
>
>
> Craig White wrote:
>
> > This line is a problem...
> >
> > <%= observe_field "states", :update => "cities",
> > :with => "city_id",
> > :url => { :controller => "test", :action => "get_cities" } %>
> >
> > should :controller => "dyn_drops" not "test"
----
It's an unusual method that is being proposed here so it's hard for me
to defend it but I think the example is just wrong...

He's got...
<%= observe_field "states", :update => "cities",
:with => "city_id", :url => { :controller => "test",
:action => "get_cities" } %>

But I would do...
<%= observe_field "states", :update => "cities",
:with => "states" :url => { :controller => "dyn_drops",
:action => "get_cities" } %>

and in the dyn_drops controller...

def get_cities(find_state)
    @cities = City.find_by_state(:all,
      :conditions => ["state = ?", find_state])
end

and that should work

Once you get this working, recognize that this was just a demonstration and that you generally want simpler view code and the logic/processing in your controllers and models.

Many thanks Craig!!

I followed your suggestions pretty closely. In the view, I changed the
observe_field statement to say:

:with => "states_id",

and in the controller, I changed the find method to say:

@cities = City.find_all_by_states_id(params['states_id'])

and it now WORKS! Woo-hoo!!

Now it's on to getting some of the crap out of my view.... :slight_smile:

Thanks again for the help.

Hi Sara,

Sara Me:

What I would really like to know at the moment
is why all of this is still not working, and where I
might be going wrong in my code.

The short answer is that you're responding to an AJAX request with an HTML
response. I found Cody Fauser's 'RJS Templates for Rails' shortcut /
tutorial (http://www.oreilly.com/catalog/rjsrails/ ) to be an excellent
investment of ten bucks.

HTH,
Bill

Sara Me,
I spent a good amount of time studying this example this morning and
have compiled a listing of what I 'think'? your code settled as, in
the hopes of replicating your brilliant technique. I enclose my
writeup at the end of this entry.
My main confusion is "How did you get the cities dropdown to
populate"? I see a get_cities.rhtml but can't imagine how it was
called UNDER the observe_field? I don't see a partial call. From what
I can see it MAGICALLY appears.
I see how the observe_field fires the controller action, but am left
in the dust as to how the get_cities.rhtml is made to appear.
I am grateful for any explanations.
Kathleen
Here's my best synopsis (below)
I am trying to create dynamic dropdowns in a form, such that when a
user
clicks an item in select list #1 (States), the options in select list
#2
(Cities) will update to reflect only cities that are in the selected
state.

I know this can be done with Javascript, but I came across a little
snippet online (see <a href="http://nealenssle.com/blog/2007/04/12/how-
to-dynamically-update-form-..."
target="_blank">this blog by Neal Enssle</a>) that does it all in
Rails
and that would be amazing if I could get it to work because I'm really
not big on JS.

I can't get this code to work. When I select a state in the first
dropdown, the 2nd dropdown does not populate.

Here is the code in controllers/dyn_drops_controller.rb:

def get_cities(find_state)
    @cities = City.find_all_by_states_id(params['states_id'])
end

Here is the code in views/dyn_drops/index.html.erb:

     <form>

       <select id="states" name="states">
           <option value="0"></option>
           <option value="1">New York</option>
           <option value="2">California</option>
           <option value="3">British Columbia</option>
           <option value="4">Ontario</option>
       </select>

<%= observe_field "states", :update => "cities", :with =>
"states_id" :url => { :controller => "dyn_drops", :action =>
"get_cities" } %>

       <br />

       <select id="cities" name="cities">
           <option value="0"></option>
           <option value=1">test option</option>
       </select>

     </form>

Here is the code in views/dyn_drops/get_cities.rhtml:

     <% for city in @cities -%>
         <option value="<%= city.id %>"><%= city.name %></option>
     <% end -%>

I also have a model named city.rb. One MySQL db table is named
'states'
and contains fields 'id' and 'name'. The other db table is named
'cities' and contains fields for 'id', 'name', and 'state_id'.

Hi Kathleen,

That's funny, because last night after I solved this I was trying to
explain it to my husband (also a programmer) and he asked me the same
thing. I felt a little foolish when I realized I had no good answer for
him, beyond saying Rails just makes it happen!! Being more of a literal
type himself, he definitely wasn't satisfied with that explanation.

As the authors of Agile Web Development with Rails freely admit, there
is some "magic" involved with Rails. I guess I can accept that for now,
though I too would love to know how they do it. Maybe because the
partial is named exactly the same as the action contained in the
controller??

Here's the really crazy part: When I rename the 'get_cities.rhtml' to
'_get_cities.rhtml' (which is how a partial should be named), the app
breaks again. Go figure. So unfortunately I can't tell you why or how
it works, but I am glad it does. Rails magic.

KathysKode@gmail.com wrote:

Hi Kathleen,

That's funny, because last night after I solved this I was trying to
explain it to my husband (also a programmer) and he asked me the same
thing. I felt a little foolish when I realized I had no good answer for
him, beyond saying Rails just makes it happen!! Being more of a literal
type himself, he definitely wasn't satisfied with that explanation.

As the authors of Agile Web Development with Rails freely admit, there
is some "magic" involved with Rails. I guess I can accept that for now,
though I too would love to know how they do it. Maybe because the
partial is named exactly the same as the action contained in the
controller??

Here's the really crazy part: When I rename the 'get_cities.rhtml' to
'_get_cities.rhtml' (which is how a partial should be named), the app
breaks again. Go figure. So unfortunately I can't tell you why or how
it works, but I am glad it does. Rails magic.

Craig,

I am going to get the e-book, especially since you are now the 2nd
person to mention it. I forgot about the 'render :partial' thing --
duh!! Like I said, I'm def a newbie.

I wasn't sure about the quality of the example either... that's the
problem with getting code snippets on the web: If you're not
experienced, it can be really difficult to tell the treasure from the
trash. In this case, I was having a hard time finding anything that
made sense to me, so I am glad I stumbled upon that example to get me
started.

Anyway thanks for the advice everyone!! This forum is such a great
resource.

Sara Me,

I'd also recommend RailsCasts by Ryan Bates - http://railscasts.com/

RailsCast #88 Dynamic Select Menus - http://railscasts.com/episodes/88
- deals with this exact subject.

Thanks Mike! Will check it out!!

Mike wrote: