Topic: Multi-stage dynamic form with observe_form

Wow.  I've been wrestling with this one for a couple hours now with no end in sight.  I am trying to present a form where the only field intially visible is 'type'.  Types is a select which is populated.  Based on what they select for type, I then want to display another select, 'item'.  This goes on a couple more levels.  I got it to the point where it displays the items (I start with an empty div in the template, and when I get the the value of type in the controller, I go through an rjs to update the div), but when I select an item, I'm not getting a callback to my controller at all.  I'm not sure if I'm going about this the right way.  Anybody have any pointers?

thanks,
brian

BrewControl.com - Brewery and Brewpub management powered by RoR

Re: Multi-stage dynamic form with observe_form

Do you have a column called "type" in the model? This is a reserved word for Single Table Inheritance. Try renaming it and seeing if some of the problems go away.

Edit: Though, if the problem is with the Javascript not calling the controller, it's probably not related. Can you post the view/form code? And just to ask the obvious, did you include the prototype libraries?

Last edited by ryanb (2007-01-19 12:26:45)

Railscasts - Free Ruby on Rails Screencasts

Re: Multi-stage dynamic form with observe_form

I worked up a more simple example, and am seeing exactly the same thing.  prototype.js is being brought in. 
Here is my controller:

class AjaxTestController < ApplicationController
  def index
    @item_types = [ "A", "B" ]
    @items = []
  end
  def add_item_changed
    p params
    if params[:line_item][:item_type] == "A"
      @items = [ "a_a", "a_b" ]
    else
      @items = [ "b_a", "b_b" ]
    end
  end
end

Here is my rjs:
page.replace_html 'type_selected', :partial => 'items'

Here is my index view:
<fieldset><legend>Add Line Item</legend>
  <% form_tag( { :action => 'add_line_item' }, { :id => :line_item }) do %>
    <div class="row">
      <span class="label">Item Type:</span>
      <span class="formw"><%= select 'line_item', 'item_type', @item_types, { :include_blank => true } %></span>
    </div>

    <div id="type_selected"></div>
     
    <div class="row">
      <%= submit_tag 'Add' %>
    </div>
  <% end %>
</fieldset>

<%= observe_form :line_item, :url => { :action=> 'add_item_changed' }, :update => {} %>


And, here is my partial:
    <div class="row">
      <span class="label">Item Name:</span>
      <span class="formw"><%= select 'line_item', 'item_id', @items %></span>
    </div>

It will display the secondary select box, and update it if I change the primary option, but if I change the secondary select, it doesn't post anything to the server.

thanks,
brian

BrewControl.com - Brewery and Brewpub management powered by RoR

Re: Multi-stage dynamic form with observe_form

Oh, I forgot...  The column name is item_type, not type.

BrewControl.com - Brewery and Brewpub management powered by RoR

Re: Multi-stage dynamic form with observe_form

Printing output (as you are doing with params in add_item_changed) has been known to cause problems with javascript - it can alter the HTTP headers which are sent back.

However, it doesn't look like this is your problem since the first select box works. I think the problem is actually a limitation of observe_form and prototype. I'm assuming (complete guess here) that prototype adds a little observer to all fields in the form when the javascript from observe_form is run (when the page loads). The select field you add later does not have this little observer so nothing happens upon changing it. You may want to use observe_field instead, and add an observe_field call the same time you add the select box. Or even better, add some javascript (remote_function) to the "onchange" attribute of each select menu instead of using the observe helpers.

Railscasts - Free Ruby on Rails Screencasts

Re: Multi-stage dynamic form with observe_form

Ryan, thanks for the reply.  Using multiple observe_fields was my first approach.  However, once I got to the second one, I realized that the post back to the server doesn't have the context for the 'parent' fields, which I need to make the appropriate db queries to figure out what data to return.  I might be able to get that to work using hidden fields and the :with clause, but that would get ugly damn quick.
It doesn't seem like what I'm trying to do should be that complicated.

Anybody else?

BrewControl.com - Brewery and Brewpub management powered by RoR

Re: Multi-stage dynamic form with observe_form

How about this, use form_remote_tag instead of form_tag for the form. Then set the "onchange" attribute of the select boxes to trigger the onsubmit action of the form:

<%= select 'line_item', 'item_type', @item_types, { :include_blank => true }, { :onchange => "$('line_item').onsubmit();" }%>

Assuming the form still has the id "line_item". Does that work for you?

Railscasts - Free Ruby on Rails Screencasts

Re: Multi-stage dynamic form with observe_form

I kind of got that working with the first level.  By using the onsubmit, how am I going to be able to distinguish between changing fields and them actually clicking the button?

BrewControl.com - Brewery and Brewpub management powered by RoR

Re: Multi-stage dynamic form with observe_form

Hmm, you could use javascript to change the value of a hidden field upon clicking the submit button. Or you could call the form "submit()" action which will bypass the "onsubmit" and submit it like a regular form - in that case it wouldn't be AJAXy.

Railscasts - Free Ruby on Rails Screencasts

Re: Multi-stage dynamic form with observe_form

Okay, I got my test form working.  IMO, it involves way too much JS, and I'm a JS moron.  I ended up using two hidden fields.  The first is to determine if it's a real click, and the other is to tell me what field was changed (very helpful for hiding otherwise invalid options if they change a higher-level option.)  Here's the new view code, in case it would be helpful for others to understand:

<fieldset><legend>Add Line Item</legend>
  <% form_remote_tag :url => { :action => 'add_item_changed', :id => 33 }, :html => { :name => 'line_item_form' }, :update => {} do %>
    <div id="add_line_item_form">
      <%= hidden_field 'button_clicked', 'was', { :id => 'button_clicked' } %>
      <%= hidden_field 'field_changed', 'name', { :id => 'field_changed' } %>
   
    <div class="row">
      <span class="label">Item Type:</span>
      <span class="formw"><%= select 'line_item', 'item_type', @item_types,
        { :include_blank => true }, { :onchange => "document.getElementById('button_clicked').value='false';" +
          "document.getElementById('field_changed').value='item_type';$(line_item_form).onsubmit();" } %></span>
    </div>

    <div id="type_selected"></div>
   
    </div>
   
    <div class="row">
      <%= submit_tag 'Add', { :onclick => "document.getElementById('button_clicked').value='true';" +
        "new AJAX.Request('ajax_test/add_item_changed/33', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" } %>
    </div>
   
  <% end %>
</fieldset>


The part I hate the most is hardcoding the url in the AJAX.Request.  Anyone have a tip for correcting that?

Coming soon: BrewControl.com - a Rails app for Brewery and Brewpub management smile

BrewControl.com - Brewery and Brewpub management powered by RoR

Re: Multi-stage dynamic form with observe_form

You can use url_for in there:

"new AJAX.Request('#{url_for(:action => 'add_item_changed')}'..."

Actually, I think you can replace this AJAX.Request call with remote_function.

Railscasts - Free Ruby on Rails Screencasts