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/&quot; 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/&quot; 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 - #88 Dynamic Select Menus - RailsCasts - deals with this exact subject.

Thanks Mike! Will check it out!!

Mike wrote: