Search code examples
ruby-on-railsrubynested-formsnested-attributesruby-on-rails-2

Issue updating class in combobox from nested form


I created a combobox that show suppliers so when supplier is selected will update a div showing purchases , after showing purchases will be in combobox but in a nested form so when I select a purchase will show me the amount of the purchase selected.

The problem is that when I select a purchase only works in the first line and when I select another combobox line show the amount of the first combobox selected.

Here my tables

suppliers
  |id|  |name|
   1     Supplier A
   2     Supplier B

shopping_documents
  |id|  |supplier_id|   
   1         1
   2         2

shopping_products
   |id|  |shopping_document_id| |qty| |purchase_product_id|
     1             1              1           1
     2             1              1           2 

purchase_products
   |id|  |name|    |price_sale| 
     1     XP          1000        
     2     VISTA       2000         

supplier_products
   |id|  |supplier_id|   |amount| |purchase_product_id
     1     1                1000        1
     2     1                2000        1 
     3     2                3000        2 

Here is the controller /app/controller/purchase_product_controller.rb

class ShoppingDocumentController < ApplicationController
 def new
   @document = ShoppingDocument.new
   @suppliers= Supplier.all    
   @products = SupplierProduct.find(:all,:conditions=>['supplier_id =? ',params[:suppliers] ],:group=>['purchase_product_id'])
   @purchases= SupplierProduct.find(:all,:conditions=>['supplier_id =? ',params[:suppliers] ],:group=>['purchase_product_id'])

   4.times do
     shopping_product = @document.shopping_products.build
   end 
 end

 def create
   @document = ShoppingDocument.new(params[:shopping_document])
   if @document.save
     flash[:notice] = "Successfully created document."
     redirect_to :action=>"index"
   else
     render :action => 'new'
   end
 end

 def update_supplier_div
   @products = SupplierProduct.find(:all,:conditions=>['supplier_id =? ',params[:suppliers] ],:group=>['purchase_product_id'])
 end

 def update_product_div
   @purchases= SupplierProduct.find(:all,:conditions=>['purchase_product_id =? ',params[:product] ],:group=>['purchase_product_id'])
 end

end

Here models:

class ShoppingDocument < ActiveRecord::Base
   has_many :shopping_products
   accepts_nested_attributes_for :shopping_products ,:allow_destroy => true
end

class ShoppingProduct < ActiveRecord::Base
   belongs_to :shopping_document
   belongs_to :purchase_product
end

class PurchaseProduct < ActiveRecord::Base
   has_many :shopping_products
   has_many :supplier_products
end

class SupplierProduct < ActiveRecord::Base
   belongs_to :purchase_product
end

Here is the view: /app/view/purchase_product/new.html.erb

<% form_for @document, :url => {:controller=>"shopping_document",:action=>'create'} do |f|  %>
   Name: <%= select_tag 'suppliers',"<option value=\"\">Select</option>"+options_for_select(@suppliers.collect {|t| [t.name,t.id]} ),:onchange=>remote_function(:url=>{:controller=>"shopping_document",:action=>"update_supplier_div"},:with=>"'suppliers=' + $('suppliers').value"),:name=>"shopping_document[supplier_id]" %>    
   <% f.fields_for :shopping_products do |builder| %>
     <%= render "shopping_product_fields", :f => builder %>
   <% end %>
   <p><%= link_to_add_fields "Add Product", f, :shopping_products %></p>
   <p><%= f.submit "Submit" %></p>
<% end %>

Here is the partial view: /app/view/shopping_document/_shopping_product_fields.html.erb

<div class="fields">
 <table>
  <tr><td><%= f.text_field :qty , :size=>"9"  %></td>
      <td><%= select_tag "product","<option value=\"\">Select</option>"+options_for_select(@products.map { |c| [c.amount, c.id] }),:onchange=>remote_function(:url=>{:controller=>"shopping_document",:action=>"update_product_div"},:before=>"load_close('loading_condition')",:success=>"load_off('loading_condition')",:with=>"'product=' + $('product').value"),:class=>"product" %></td>
      <td><%= f.select :purchase_product_id,"<option value=\"\">Select</option>"+options_for_select(@purchases.map { |c| [c.amount, c.id] }), {}, :class => "helping" %></td> 
      <td><%= link_to_remove_fields image_tag("standard/delete.png",:border=>0,:size=>"14x14"), f %></td>
  </tr>
 </table>
</div>

Here is the view rjs : /app/view/shopping_document/_update_supplier_div.rjs

page.select('.product').each do |select_tag|
  page.replace_html select_tag, "<option value=\"\">Select</option>"+options_for_select(@products.map{|c| [c.purchase_product.name, c.purchase_product_id]})
end

Here is the view rjs : /app/view/shopping_document/_update_product_div.rjs

page.select('.helping').each do |select_tag|
  page.replace_html select_tag,"<option value=\"\">Select</option>"+options_for_select(@purchases.map { |c| [c.amount, c.id] })
end

Is not updating in other lines just updating in the first line.

Please somebody can help me with this issue?


Solution

  • The problem is that you always send the same value to your controller action when updating products.

    Look at this line from your /app/view/shopping_document/_shopping_product_fields.html.erb partial:

    <%= select_tag "product",
      "<option value=\"\">Select</option>"+
      options_for_select(@products.map { |c| [c.amount, c.id] }),
      :onchange=> remote_function(
        :url= {:controller => "shopping_document", :action=>"update_product_div"}, 
        :before => "load_close('loading_condition')", 
        :success => "load_off('loading_condition')", 
        :with => "'product=' + $('product').value"),
        :class=>"product" %>
    

    From what I remember, in prototype.js, when you use $("something"), it will find the first element in your DOM tree with id = 'someting'

    The fact you're using $('product') in the with clause makes it always send the select tag value of the first row to the server, which is wrong.

    You should be using $(this) instead to make sure it's the value of the current select that is being sent. So you need to replace:

    :with => "'product=' + $('product').value"
    

    With this one:

    :with => "'product=' + $(this).value"
    

    Also another problem is that when you change the product, it changes all the prices at once, and you only want that product's price to change. So, you need to be more specific about what line to change in your rjs.

    The way we are going to do that is by changing the rjs you return. This rjs will now just assign a javascript variable:

    page.assign "prices_for_product", "<option value=\"\">Select</option>" +  options_for_select(@purchases.map { |c| [c.amount, c.id] })
    

    And now we have to create a new javascript method that will use the values in that variable and set it to the current element. For that you need to add the following method in the javascript file where you defined load_close and load_off:

    function update_prices_for_product(prices_select_tag){
      prices_select_tag.innerHTML = prices_for_product;
    }
    

    But we still need to call this method when the AJAX call succeeds so we're going to add it to the :success parameter.

    :success => "load_off('loading_condition'); update_prices_for_product($(this));"
    

    There is also something that is really confusing me. You are sending the supplier_product_id that comes from your nested form to your controller, and injecting it in your query as a purchase_product_id, all that called on the SupplierProduct table. From what I can see on your data model that's not correct and I must admit I'm really confused. See this in your update_product_div action:

    SupplierProduct.find(:all,:conditions=>['purchase_product_id =? ',params[:product] ],:group=>['purchase_product_id'])
    

    What you're looking for are all the purchases done for a product. That would be something more like:

    PurchaseProduct.find(:all,:conditions=>['supplier_product_id =? ', params[:product]])
    

    You're clearly missing the supplier_product_id column on your purchase_products table.

    That's as much as I can do for you, let's hope someone else jumps in...

    I must say that this is really not good practice and you should use unobstrusive javascript all the time and upgrade to jquery. Moreover, use a more up-to-date version of Rails because not many people can still help you with Rails 2.3...

    The only thing good with this answer is that it might make it work...