Rails Dynamic Forms with Cascading Select - ruby-on-rails

I am trying to implement some nested form elements that have values based on a select box value in the main form. As it stands right now when I make the selection in the main form it changes the values displayed in all currently displayed nested form, but when I click the button to add a new nested form element, the new form elements display values that are not based on the primary select. What I need is for any time the button is clicked to add a new nested form, the values in that form should be based on the select box in the main form.
Here are the steps and what is happening:
Here are the steps and the desired results:
Here is what my partial looks like. This is what is displayed when the "new" button is clicked:
<tr class="details_row nested-fields">
<td><%= f.select :area_id, options_for_select(#areas.collect {|c| [ c.area_name, c.id ] }), {include_blank: true}, {:class => 'form-control area_select'} %></td>
<td><%= f.select :product_id, options_for_select(#products.collect {|p| [ p.product_number, p.id ] }), {include_blank: true}, {:class => 'form-control product_select'} %></td>
<td><%= f.number_field :quantity, class: 'form-control', "min" => 1 %></td>
<td><%= f.select :accessory_id, options_for_select(#accessories.collect {|p| [ p.product_number, p.id ] }), {include_blank: true}, {:class => 'form-control accessory_select'} %></td>
<td><%= f.text_field :notes, class: 'form-control' %></td>
<td><%= link_to_remove_association '<i class="fa fa-trash"></i>'.html_safe, f %></td>
</tr>
All of this action is when I'm creating a new record, here is the code from my controller for the new method:
def new
if current_user
#estimate = Estimate.new
#estimate.estimate_details.build
#default_user = current_user.id
#product_type = 3
#areas = Area.where("product_type_id = ?", #product_type)
#products = Product.select("product_number, id").where("product_type_id = ? AND active = 't' ", #product_type)
#accessories = Product.select("product_number, id").where("product_type_id = ? AND active = 't' AND accessory = 't'", #product_type)
else
redirect_to root_path, notice: 'You are not logged in.'
end
end
This is the method from the controller (the same controller as new above) that updates the select boxes:
def update_select
#product_type = params[:product_type_id]
#areas = Area.where("product_type_id = ?", params[:product_type_id])
#products = Product.where("product_type_id = ? AND active = 't'", params[:product_type_id])
#accessories = Product.where("product_type_id = ? AND active = 't' AND accessory = 't'", params[:product_type_id])
respond_to do |format|
format.js
end
end
I was thinking that #product_type would be set to the default in the "new" method of the controller. When the update_select method is run, it should update the #product_type and this would be used when the new elements were created...but that didn't work.
Update
Here is my update_select.js. This does update the three select boxes in the nested form, but I have to add a dynamic form and then make a selection in the main form select box in order for the fields to be correct for the newly added form details.
$(".area_select").empty().append("<%= escape_javascript(render(partial: "select_boxes/area", collection: #areas)) %>")
$(".product_select").empty().append("<%= escape_javascript(render(partial: "select_boxes/product", collection: #products)) %>")
$(".accessory_select").empty().append("<%= escape_javascript(render(partial: "select_boxes/accessory", collection: #accessories)) %>")
Here are the partials referenced by the above js:
Accessory:
<option value="<%= accessory.id %>"><%= accessory.product_number %></option>
Product:
<option value="<%= product.id %>"><%= product.product_number %></option>
Area:
<option value="<%= area.id %>"><%= area.area_name.titleize %></option>
And just for good measure here is the script that triggers the update_select
$ ->
$(document).on 'change', '#product_type_select', (evt) ->
$.ajax
url:'/estimates/new/update_select',
type: 'GET',
dataType: 'script',
data: {
product_type_id: $("#product_type_select option:selected").val()
}
error: (jqXHR, textStatus, errorThrown) ->
console.log("AJAX Error: #{textStatus} #{errorThrown}")
success: (data, textStatus, jqXHR) ->
console.log("Dynamic area select OK!")

Related

How to display a selection based on user input using ajax and jquery

Think of the below as a bike rental. Someone fills out a form and gets a bike assigned to them which they can rent and borrow for a certain amount of time.
The problem I am having is I am trying to show the person who wants to rent the bikes what bikes are available before they submit the form. Below is my attempt using ajax. I have no errors but also my select is not updating.
request controller methods below
def new
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
#new_request = Request.new
end
create method below (with a temporary workaround, that reloads the form with a warning about availability.)
def create
#request = Request.new(request_params)
available_bikes = #request.new_request(current_user.id)
if (available_bikes >= #request.number_of_bikes_wanted) && #request.save
redirect_to root_path
else
flash[:warning] = "You have requested more bikes than available. There are only #{available_bikes} bikes available"
redirect_to new_request_url
end
end
params in request controller
def request_params
params.require(:request).permit(:Borrow_time, :Borrow_date,
:Return_date, :Return_time,
:number_of_bikes_wanted, bike_ids: [])
end
new.html.erb view
<div class="form" align = "center">
<%= render 'form.js.erb' %>
</div>
_form.js.erb below
<script type="text/javascript">
$(document).ready(function() {
$('.my-date').on('change', function() {
var data = {}
$('.my-date').each(function() {
if($(this).val()) {
data[$(this).attr("id")] = $(this).val();
}
});
if(Object.keys(data).length > 1) {
$.ajax({
type: "POST",
url: <%= new_request_path %>,
data: data
});
}
});
});
var options = "";
<% #bikes.each do |bike| %>
options += "<option value='<%= bike.id %>'><%= bike.name %></option>"
<% end %>
$('#request_number_of_bikes_wanted').html(options);
</script>
<div class="block-it" align=center>
<br>
<%= form_for #new_request do |request| %>
<%= request.label :Borrow_date, 'Borrow on' %>
<%= request.date_field :Borrow_date, id: 'Borrow_date', class: 'my-date', min: Date.today, :required => true %>
<%= request.label :Borrow_time, 'Borrow at' %>
<%= request.time_field :Borrow_time, value: '10:00', min: '9:00 AM', max: '4:30 PM', default: '10:00 AM', :ignore_date => true, :required => true %>
<br><br>
<%= request.label :Return_date, 'Return On' %>
<%= request.date_field :Return_date, id: 'Return_date', class: 'my-date', min: Date.today, :required => true %>
<%= request.label :Return_time, 'Return at' %>
<%= request.time_field :Return_time, value: '10:00', min: '9:00 AM', max: '4:30 PM', default: '10:00 AM', :ignore_date => true, :required => true %>
<br><br>
<br><br>
<%= request.label :NumberOfBikesWanted, 'Number of bikes' %>
<%= request.select :number_of_bikes_wanted, %w(select_bike), :required => true %>
<br>
<%= request.submit 'Submit' %>
<%= request.submit 'Reset', :type => 'reset' %>
<% end %>
<br>
</div>
There are a two main problems with your code:
Controller
Use a different action to set the endpoint that you will call with ajax, so instead of this:
def new
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
#new_request = Request.new
end
Try this:
def bikes
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
def new
#new_request = Request.new
end
If you want to keep REST routes, then create a new controller and use the index action within that controller.
Form
This code:
var options = "";
<% #bikes.each do |bike| %>
options += "<option value='<%= bike.id %>'><%= bike.name %></option>"
<% end %>
$('#request_number_of_bikes_wanted').html(options);
doesn't belong here, it must be deleted from your file and instead put it on a new file called bikes.js.erb; also rename your form to _form.html.erb.
And update your ajax call to use your new route:
$.ajax({
type: "POST",
url: <%= bikes_path %>,
data: data
});
What you want to setup is a new endpoint but instead of returning html, it will return a js. But you must treat it as an independent action, just as any other action in rails. The only difference is how you call that action (ajax) and how you respond to it (js).

Ruby on Rails select2 AJAX/JQuery - passing a parameter to controller not working

I'm trying to make a Job descriptions (JD) page to compare between a choose JDs.
My main page will have a field to select some jobs, and a button/link to show the selected jobs details in a table below the selection field using select2 with RoR, as in the image
Job descriptions Viewer .
My issue is that I cannot pass the selected Jobs IDs to the controller, and I get this message:
Completed 406 Not Acceptable in 20ms (ActiveRecord: 0.0ms)
ActionController::UnknownFormat (ActionController::UnknownFormat):
app/controllers/job_descriptions_controller.rb:81:in `updateJobs'
My controller method :
def updateJobs
#selected = JobDescription.where(id: params[:selectJdField2])
respond_to do |format|
format.js
format.html
end
end
The main View (jdComparison.html.erb) will render two partials
<h1>Listing Job Descriptions</h1>
<%= render 'sidebar' %>
<div class="clearfix"></div>
<div>
<%= render partial: 'item_list', locals: { job_desc: #job_descriptions} %>
</div>
The _sidebar.html.erb partial has selet2 field and a link to refresh the Jds that called "Find Link":
<div class="col-sm-8">
list of JDs:
<%= select_tag "selectJdField", options_from_collection_for_select(#job_descriptions, :id, :job_title), { :multiple => true } %>
</div>
<%= link_to "Find Link", updateJobs_path(#job_descriptions), :remote => true %>
<script>
$(document).ready(function() { $("#selectJdField").select2(); });
</script>
The _item_list.html.erb partial will view all JDs have been chosen in the select2 field:
<div>
<table>
<thead>
<tr>
<th>Job title</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<% job_desc.each do |job_description| %>
<tr>
<td><%= job_description.job_title %></td>
<td><%= job_description.department %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
updateJobs.js.erb, should refresh the JDs list when I click the "Find Link" button (I think my first issue is here)
$("#div_id").html("<%= escape_javascript(render partial: 'item_list', locals: { job_desc: #selected}) %>")
The JS file (I think my second issue is here):
$ ->
updateLists = () ->
$.ajax
url:'updateJobs'
type: 'post'
dataType: 'html'
format: 'js'
data: {
selectJdField2 : $('#selectJdField').val()
}
The routes:
get 'updateJobs' => 'job_descriptions#updateJobs'
post 'updateJobs' => 'job_descriptions#updateJobs'
When I replace the controller method with this:
def updateJobs
#selected = JobDescription.where(id: 1)
end
it will give me the details of JD number 1 after clicking the Find Link. Kindly, I need your help..
This what I've done to make it works:
I have relapsed the "Find Link" with a normal button:
<button id="btnFind">Find</button>
I added "id" to the first div in _item_list.html.erb partial
<div id = "div_id">
In js file:
$ ->
$(document).on 'change', '#selectJdField', (evt) ->
updateLists()
$(document).on 'click', "#btnFind", (evt) ->
updateLists()
updateLists = () ->
$.ajax '/updateJobs',
type: 'GET'
dataType: 'script'
data: {
selectJdField2 : $('#selectJdField').val()
}
and I didn't need (post 'updateJobs') in the routes file.. Now, everything work fine...Thank you "Venkat Ch"

Select option required Ruby Rails

I have a mix of ruby rails code
I have a form with a selection option that i want to be a required, and i want to validate. If user do not select anything i want to validade with a error message.
However, I try to copy past code from internet, I'm new at ruby rails and I still not have a error message.
I also check that i use 'required' , or if i use 'validates_presence_of' doesn't make difference because it's a submit form (i think)
test_filteR_form.rb
class TestFilterForm < ApplicationForm
attribute :model, String
validates_presence_of :model
end
.html.erb
<%= f.input :fill_form_error_message,:as => :hidden, :input_html => { :value =>I18n.t('test.fill_form_error') } %>
<%= f.input :model, label: I18n.t('test.filters.model'), autofocus: true, input_html: {class: 'input-xlarge chosen-select' }, collection: TestType.options_for_select, include_blank: true %>
"/>
controller
def paginate
#test_form = TestForm.new(params)
unless #test_form.valid?
#model = params[:test_filter_form][:model]
#h_model = #model.pluralize + 'H'
#history, _query, #test_fields = TestQueryService.search!(params)
session[:test_query] = _query
session[:test_klass] = #model
else
format.json { render :json => { :error => #test_form.errors.full_messages }, :status => 422 }
end
js.coffee
$contentDiv.on 'ajax:error', 'form[data-value]', (event, xhr, status, error) ->
data = $.parseJSON(xhr.responseText)
$result = $(#).parents('tr').find('[data-result]')
controller.resultUpdateError xhr.status, $result.data('result'), data, $(#)
# Hide row loading spinner
$(#).parents('tr').find('span[role="result_update_spinner"]').hide()
# Hide saved form
$(#).parents('tr').find('.saved_form').hide()
resultUpdated: (result, data, $form) ->
if data.flash != undefined
# Sets a sucess message on page top
flash data.flash.type, data.flash.message
# Sets a success message on row
$fieldForm = $form.parents('tr').find(".messages")
$fieldForm.find('.controls').empty()
$fieldForm.find('.control-group .controls').css('color', 'green').append #_inlineMessage("Gravado com sucesso")
# Hide success message after some time
setTimeout ((self) ->
->
$fieldForm.find('.control-group .controls').empty()
return
)(this), 4000
Since you are dynamically created the selection box then there must be a default value selected which is not nil so there is no change seen you can manually create selection like this:
<div class="form-group">
<%= f.label :select_user_country %><br/>
<select class="form-control select2" name="user[country_id]">
<option value="" selected disabled>Select a Country</option>
<%#countries.each do |country|%>
<option value="<%=country.id%>"><%=country.name%></option>
<%end%>
</select>
</div>

ROR application - pass data from view to controller

Trying to pass data from a view to controller using Javascript. How do I do this ? Specifically the information I wish to access in the controller is what option/s have been selected in a variable number (one to many) of selections on the screen.
I have a ROR application I am developing and on the "Companies - Drill Interests" screen I wish to use java/coffeescript to send data and invoke controller action. In the controller I can't get access to all the data I need to respond. That is I can' determine what "Saved Assumption" the user has selected.
From the Companies controller building array (should I be doing this or using another method) of saved assumptions.
def companies_drill_interests
# intial display use first (if present) user drill evaluations
#result_list = Array.new
#company_listings.each do |cl|
#drill_interests.each do |di|
target_share_price = 0
result = Hash.new
result["cl_display_name"] = cl.display_name
result["di"] = di.drill.name
result["target_share_price"] = 0
# if saved assumptions grab first assumption and then first target_share_price
#matched_evaluation_assumption =
EvaluationAssumption.matched_eval_assum(#current_user.id, di.drill_id)
...
# code that builds result ...
end
end
#result_list << result
end
end
end
from the screen partial views/companies/_companies_drill_interests
<%= simple_form_for #company,
html: { class: 'infogroup',
id: "x_company_drill_interests"} do |f| %>
<div class="infogroup-header">Drill Interests</div>
<div class="infogroup-body">
<table border="0" cellpadding="0" cellspacing="0" class="info">
<th class="very_large_column lalign">Drill Name</th>
...
<% if #drill_interests.present? %>
<% index = 0 %>
<% #drill_interests.each do |drill_interest| %>
<%= fields_for "drill_interest[]", drill_interest do |di| %>
<tr>
<td><%= drill_interest.drill.name %></td>
<td class="ralign"><%= drill_interest.equity_percentage %></td>
<% array_name = "evaluation_assumption" + index.to_s %>
<td id="x_user_eval_assum" class='lalign large_column' data-targets="<%= #probability_json %>">
<td> <%= select("mea_user_save_name", "id",
#assumptions[array_name].collect {|r| [ r["mea_user_save_name"], r["id"] ] },
{ :include_blank => false }) %> </td>
<% index += 1 %>
</tr>
<% end %>
...
If the user changes the "Saved Asumptions" selection this is detected in jave/coffeescript. From app/assets/javascript/companies.js.coffee
...
$('#x_company_drill_interests').change((event) ->
company_id = $('.form.companies_drill_interests').attr('data-companyid')
event.preventDefault()
calculateResult company_id
)
calculateResult = (company_id)->
data = $('#x_company_drill_interests').serialize()
$.ajax
url:"/companies/#{company_id}/projection.json",
type:"post"
...
Display statement in Companies Controller for when the action I can't get to work is performed.
class CompaniesController < ApplicationController
...
def projection
puts "================================="
puts "params = " + params.to_s
puts "================================="
...
In the logs output from this statement
params = {"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"rFx8TBWnECUCxFiEBQ+0lTmkvhUKEJrUx/SlEQNPEP8=", "mea_user_save_name"=>{"id"=>""}, "action"=>"projection", "controller"=>"companies", "id"=>"18", "format"=>"json"}. The arrays are not present.
Overview of relationships between models.
Company has 0 to many drill_Interests. Each Drill_interest has 0 to many Evaluation_Assumptions (and using Evaluation_Assumptions I wish to display User_Evaluation_Results).
If you wish to see above you can visit the application as per instructions below.
1- go to http://quiet-fortress-3338.herokuapp.com/users/login
2- login with userid = pmlc and password = gmfive99
3- select companies from menu top RHS
4- select ADO
5- Select screen "Companies Drill Interests"
You will see ADO has an interest in both "Temp-01" and "ZZZ-Unlimited" with a number of saved assumptions for both
You are using fields_for, so change:
<td> <%= select("mea_user_save_name", "id",
#assumptions[array_name].collect {|r| [ r["mea_user_save_name"], r["id"] ] },
{ :include_blank => false }) %> </td>
to
<td> <%= di.select("mea_user_save_name", "id",
#assumptions[array_name].collect {|r| [ r["mea_user_save_name"], r["id"] ] },
{ :include_blank => false }) %> </td>
Try to use select_tag instead of select
select_tag statement
select_tag "whatever_name", options_from_collection_for_select(#assumptions[array_name].collect {|r| [ r["mea_user_save_name"], r["id"] ] }, "id", "mea_user_save_name"), include_blank: true
To send and array to backend using fields the fields must be named
<input name="myfieldarray[]">
In this way form.serialize() sends an array for myfieldarray.
check this question
How to pass an array from view to controller in Ruby on Rails
try this.
select_tag "my_array_param[]", options_from_collection_for_select(#assumptions[array_name].collect {|r| [ r["mea_user_save_name"], r["id"] ] }, "id", "mea_user_save_name"), include_blank: true

Filter field based on previous field selection

How can I use simple_form to filter a field, based on a previous fields value?
For instance, I have an Opportunities form, with two fields, Company and Contact.
Company Field:
<div class="form-group">
<%= f.association :company, collection: Company.all.order(:account), prompt: "", :label_method => :account, :value_method => :id %>
</div>
Contact Field:
<div class="form-group">
<%= f.association :contact, collection: Contact.all.order(:first_name), prompt: "", :label_method => lambda { |contact| "#{contact.first_name} #{contact.last_name}" }, :value_method => :id %>
</div>
Here is what I want to do: If I select a company called "Deviant" from the Company field above, I want the Contact field to only display those contacts associated with the company called "Deviant".
I am trying something like this, but can't get it to work:
<div class="form-group">
<%= f.association :contact, collection: Contact.where("company_id = ?", params[:id]), prompt: "", :label_method => lambda { |contact| "#{contact.first_name} #{contact.last_name}" }, :value_method => :id %>
</div>
I don't know how to reference the value in the Company field.
How can I do this?
Thanks.
Update
Anyone? Surely this must be possible. This is a key functionality in any form. I would hope I don't need jQuery or something.
I think the best approach is to use ajax requests to update your contacts collection dinamically whenever the company's selected value is changed.
First you'll need an action in your contacts controller:
app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def contacts_list
if params[:company_id]
#contacts = Contact.where(company_id: params[:company_id])
else
#contacts = Contact.all
end
respond_with(#contacts) do |format|
format.json { render :json => #contacts.to_json(:only => [:id, :first_name, :last_name]) }
end
end
end
Add this to your routes:
config/routes.rb
post 'contacts_list' => "contacts#contacts_list", as: :contacts_list
Then use the coffeescript code bellow to populate your contacts' collection:
app/assets/javasctipts/companies.js.coffee
$(document).ready ->
if $("#opportunity_company_id")
populate_contacts()
$("#opportunity_company_id").change ->
populate_contacts()
populate_contacts = ->
$contacts_select = $("select#opportunity_contact_id")
$contacts_select.attr "disabled", "disabled"
company_id = $("select#opportunity_company_id").val()
if company_id is ""
$contacts_select.html "<option value=\"\">(select the company first)</option>"
else
$contacts_select.html "<option value=\"\">(loading contacts...)</option>"
data = {company_id: company_id}
data[window._auth_token_name] = window._auth_token
$.ajax "/contacts_list",
type: "post"
dataType: "json"
data: data
success: (contacts) ->
_html = '<option value="">Select the contact:</option>'
_html += '<option value="'+contact.id+'">'+contact.first_name + ' ' + contact.last_name + '</option>' for contact in contacts
$contacts_select.html _html
$contacts_select.removeAttr "disabled"
error: ->
alert 'Error trying to load contacts.'
Finally, inside your html's head tag:
<% if protect_against_forgery? %>
<script>
window._auth_token_name = "<%= request_forgery_protection_token %>";
window._auth_token = "<%= form_authenticity_token %>";
</script>
<% end %>
Hope it helps...
update:
Add the following line to your ApplicationController (app/controllers/application_controller.rb):
respond_to :html, :xml, :json, :js

Resources