在编辑视图中动态生成选择菜单不保持状态

时间:2009-10-07 07:50:12

标签: javascript ruby-on-rails javascript-events

我正在关注Railscast 88以创建动态依赖下拉菜单。 http://railscasts.com/episodes/88-dynamic-select-menus

我将这些下拉列表呈现在我正在以多模型形式使用的部分中。我正在使用的表单遵循Ryan Bates的Advanced Rails Recipes流程。因为我在部分内部渲染下拉列表,所以我不得不严格遵循Railscast代码。在上面提供的Railscast链接上,注释30-31和60-62解决了这些问题并提供了我使用的方法。

对于新记录,一切都很好。我从下拉列表中选择一个父对象,javascript动态地将子选项限制为仅与我选择的父项相关联的项。我能够保存我的选择,一切都很好。

问题在于,当我返回编辑页面并单击子选择下拉列表时,将其绑定到父对象的约束不再存在。我现在能够选择任何孩子,无论孩子是否与父母有关。这是一个主要的用户体验问题,因为子对象列表太长而且复杂。我需要子选项始终依赖于所选的父级。

这是我的代码:

Controller#javascripts

def dynamic_varieties
    @varieties = Variety.find(:all)
  respond_to do |format|
    format.js
  end
end

观看#javascripts#dynamic_varieties.js.erb

var varieties = new Array();
<% for variety in @varieties -%>
  varieties.push(new Array(<%= variety.product_id %>, '<%=h variety.name %>', <%= variety.id %>));
<% end -%>

function collectionSelected(e) {
  product_id = e.getValue();
  options = e.next(1).options;
  options.length = 1;
  varieties.each(function(variety) {
    if (variety[0] == product_id) {
      options[options.length] = new Option(variety[1], variety[2]);
    }
  }); 
}

观看#users#edit.html.erb

<% javascript 'dynamic_varieties' %>
<%= render :partial => 'form' %>  

查看#users#_form.html.erb

<%= add_season_link "+ Add another product" %>  
<%= render :partial => 'season', :collection => @user.seasons %>  

查看#users#_season.html.erb

<div class="season">
<% new_or_existing = season.new_record? ? 'new' : 'existing' %>
<% prefix = "user[#{new_or_existing}_season_attributes][]" %>

<% fields_for prefix, season do |season_form| -%>
  <%= error_messages_for :season, :object => season %>
      <div class="each">
          <p class="drop">
            <label for = "user_product_id">Product:</label> <%= season_form.collection_select :product_id, Product.find(:all), :id, :name, {:prompt => "Select Product"}, {:onchange => "collectionSelected(this);"} %>
            <label for="user_variety_id">Variety:</label>
            <%= season_form.collection_select :variety_id, Variety.find(:all), :id, :name, :prompt => "Select Variety" %>
          </p>

          <p class="removeMarket">
            <%= link_to_function "- Remove Product", "if(confirm('Are you sure you want to delete this product?')) $(this).up('.season').remove()" %>
          </p>            
      </div>
<% end -%>

2 个答案:

答案 0 :(得分:2)

这是你的罪魁祸首:

<%= season_form.collection_select :variety_id, Variety.find(:all),
  :id, :name, :prompt => "Select Variety" %>

完美地在新记录上工作,因为它显示了所有内容,并且当选择在另一个选择框上发生更改时会被覆盖。

你需要做这样的事情:

<% varieties = season.product ? season.product.varieties : Variety.all %>
<%= season_form.select :variety_id, 
  options_from_collection_for_select(varieties, :id, 
  :name, season.variety_id), :prompt => "Select Variety" %>

仅使用与season.product关联的品种。如果season.product不存在,则列出所有这些。如果现有记录具有variety_id,它还将自动选择正确的记录。

改变也不会有害。

<%= season_form.collection_select :product_id, Product.find(:all), 
  :id, :name, {:prompt => "Select Product"}, 
  {:onchange => "collectionSelected(this);"} %>

<%= season_form.select :product_id,  
  options_from_collection_for_select(Product.find(:all),
  :id, :name, season.product), {:prompt => "Select Product"},
  {:onchange => "collectionSelected(this);"} %>

将在页面加载时选择正确的产品。第二部分基本上就是执行BYK第一个建议的Rails方式。但是,考虑到赋予选择框的onchange方法的性质,这条线本身不能解决问题。它只会通过突出显示与季节相关的产品来增强用户体验。

答案 1 :(得分:1)

我认为你有两个选择:

  1. 为其中一个产品(或简称产品列表的第一个元素)提供一个“选定”属性,该属性将强制浏览器始终选择该属性。
  2. 在“dom ready”或“window.onload”上触发“collectionSelected”功能,并将产品列表选择框作为参数。
  3. 并且注意:永远不要相信JavaScript强制用户将适当的数据发送到服务器。