Dayo Greats Dayo Greats - 13 days ago 6
Javascript Question

Pre-populate Rails collection_select with ajax action (Rails 5 and jQuery)

I am trying to build an invoice app where every invoice has a line invoice item(e.g. productname, cost, amount).

I want when i select productname from the from a drop-down list in the invoice item, an ajax is fired to query the product database table with params[:product_id], gets the product cost from the database and hence populate the item cost instantly.

i.e. When a user select product name, that product cost shows up too instantly without typing it.

In the attached image below, my query_string returns JSON as expected, except for i dont know how to pass/display the returned data to the cost_field.

If i do html(product.cost), i get/return [object, object] at #notification and undefind cost in the rails console too.

My question is, how do i do something like this? $("#notification").html(product.cost)

MODELS:

class Invoice < ApplicationRecord
has_many :items, dependent: :destroy
accepts_nested_attributes_for :items, allow_destroy: true, reject_if: proc {|a| a[:product_id].blank?}
has_many :products, through: :item
end

class Item < ApplicationRecord
belongs_to :invoice, optional: true
belongs_to :item, optional: true
end

class Product < ApplicationRecord
has_many :items
end


CONTROLLER

class InvoicesController < ApplicationController
before_action :set_invoice, only: [:show, :edit, :update, :destroy]


def index
@invoices = Invoice.all
end

def new
@invoice = Invoice.new
2.times { @invoice.items.build }
end

def pricedata
@product = Product.select(:id, :cost, :productname).where('products.id = ?', params[:product_id])

respond_to do |format|
format.html { render html: @product.id }
format.js #{ render js: @product}
format.json { render json: @product }
end

private
def invoice_params
params.require(:invoice).permit(:name, :amount, items_attributes: [:qty, :price, :id, :product_id, :_destroy])
end


FORM

<p id="notification"></p> # notice to keep my eyes on return obj

...

<%= f.fields_for :items do |item| %>

<span><%= item.label :product_id %> </span>


<span> // # collection begins here
<%= item.collection_select(:product_id, Product.all, :id, :productname,
{prompt: "Select a product"},
{class: 'selectors' multiple: true })
%>
</span> // # collection ends here


<span><%= item.label :qty %> </span>
<span><%= item.number_field :qty %> </span>
<span><%= item.label :price, class: "p_price" %> </span>
<span><%= item.number_field :price %> </span>
<span><%= item.check_box :_destroy %> Remove item</span>

...


JAVASCRIPT / COFFEE

$(document).ready ->
$('.selectors').on "change", ->
selectOptions= $('.selectors option:selected')
product_id = $(this).val()

if selectOptions.length > 0
selectOptions.each ->
$.ajax({
url: '/invoices/:id/pricedata?product_id=' + product_id,
type: 'GET',
dataType: 'html',
contentType: 'application/html'
success: (product)->
$("#noticification").html(product)
console.log(product)
})


enter image description here

max max
Answer

You're really overcomplicating it. To get the details for a product you should just be sending an ajax request to a simple show action on ProductsController.

class ProductsController < ApplicationController
  # GET /products/:id
  def show
    @product = Product.find(params[:id])
    respond_to do |format|
     format.html
     format.json { render json: @product }
    end
  end
end

Also you should not use a multiple select in this case as it would make the quantity and price ambiguous. Instead create a fieldset or div for each item row. The fields_for helper can be used to give a specific scope for each item:

<%= form_for(@invoice) do %>
  # ...
  <%= fields_for(:items) do |item| %>
    <!-- group each item in an element so you can find the associated inputs -->
    <div class="item">
     <!-- use a div to group inputs ands labels instead! -->
     <span><%= item.label :product_id %>      </span>
     <span><%= item.collection_select(:product_id, Product.all, :id, :productname,
            {prompt: "Select a product"}, 
            {class: 'selectors'  multiple: true })  %>
     </span>     // # collection ends here
     <span><%= item.label :qty %> </span>
     <span><%= item.number_field :qty %> </span>
     <span><%= item.label :price, class: "p_price"   %> </span>
     <span><%= item.number_field :price %> </span> 
     <span><%= item.check_box :_destroy %> Remove item</span>
    </div>
  <% end %>
  # ...
<% end %>
Comments