Dynamic dropdown in rails simple_form - javascript

I have a simple has_many and belongs_to relationship in my rails app. I'm using simple_form and want to dynamically change the dropdown options based on the value chosen by the user.
Models
class Processor < ApplicationRecord
has_many :processor_bank_accounts
end
class ProcessorBankAccount < ApplicationRecord
belongs_to :processor
end
Form inputs
<%= simple_form_for [#customer, #transaction] do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :status, :collection => ["payment request"], include_blank: false %>
<%= f.input :processor, collection: #processors ,label_method: :name,value_method: :id,label: "Processor" , include_blank: false %>
<%= f.input :processor_bank_account, collection: #bank_accounts , label_method: :bank_name, value_method: :id, label: "Processor Bank Account" , include_blank: true %>
<%= f.input :tcurrency, collection: #currencies, include_blank: false, label: 'currency' %>
<%= f.input :amount, as: :decimal, label: 'amount' %>
</div>
<div class="form-actions text-center">
<%= f.button :submit, "Add transaction", class: "form-button"%>
</div>
<% end %>
So essentially, I need the processor_bank_account dropdown to populate based on the processor chosen by the user. In the console, this would just be: ProcessorBankAccount.where(processor: processor).
Need to load options using JS and think I need to use JSON but not sure where to go from here

One way to do this would be to use jQuery to make an AJAX call to a controller action and then let Rails handle the rest through an erb template.
So on your page, with the form, invoke the action via AJAX using something like:
<script>
$(document).ready(function() {
$('#processor_id').on('change', function() {
$.ajax({
url: '/transactions/get_processor_bank_accounts',
type: 'GET',
data: {
processor_id: this.value
},
dataType: 'script',
error: function() {
alert('An error occurred retrieving bank accounts for the selected processor.');
}
});
});
});
</script>
NB, #processor_id is the id for your dropdown control.
Next, instantiate the bank accounts within your action in your controller:
def get_processor_bank_accounts
#processor_bank_accounts = ProcessorBankAccount.where(processor_id: params[:processor_id])
end
And finally simply create a view that will be responsible for repopulating your dropdown:
$select_list = $('#processor_id');
$select_list.empty();
<% #processor_bank_accounts.each do |pba| %>
$select_list.append($('<option value="<%= pba.id %>"><%= pba.name %></option>'));
<% end %>

I came up with the following solution:
1) Add a new method to my processors controller to render the inputs for the second (dynamic) dropdown in JSON format:
def processor_bank_accounts
render json: #processor.processor_bank_accounts.each do |bap|
{ id: bap.id, name: bap.name }
end
end
2) Assign this new function to a new route in config/routes:
get 'api/bankaccounts', to: 'processors#processor_bank_accounts', as: 'bankaccounts'
3) Create a JS function to access the route with the id of the processor selected in the first dropdown and populate the second dropdown with items from the JSON array:
// select first dropdown
const processor = document.getElementById("transaction_processor");
// select second dropdown
const bapSelect = document.getElementById("transaction_processor_bank_account");
function update_baps(processor_id) {
const url = `INSERTWEBURLHERE/api/bankaccounts?id=${processor_id}`;
fetch(url)
.then(response => response.json())
.then((data) => {
bapSelect.innerHTML = ""; // clear second dropdown
data.forEach((bap) => { // go through all the BAPs
const elem = `<option value="${bap.id}">${bap.bank_name}</option>`; // create option elements to insert into the second dropdown, bank_name is the chosen label method in the form
bapSelect.insertAdjacentHTML("beforeend", elem); // insert options into the dropdown
});
});
}
4) Trigger the JS when the first dropdown field is changed:
processor.addEventListener('change', function () {
update_baps(parseInt(processor.value));
});

You should add an id to the selects so you can identify them form the script.
$('select#processor').on('change', function() {
var processor_id = this.value;
var processor_bank_account = $('select#processor_bank_account')
$.ajax({
type: "GET",
url: <%= your_path %> ,
data: { processor_id: processor_id },
success: function(data, textStatus, jqXHR){
processor_bank_account.empty();
var option = new Option(data.bank_name, data.id, false, false);
processor_bank_account.append(option);
},
error: function(jqXHR, textStatus, errorThrown){...}
})
});

Related

Rails/Javascript: Data attribute not returned through partial, but present in DOM

I am trying to display the value of a select field for shipping costs beneath the field. Thanks to #Vasfed I am tying to use data attributes and javascript. But while the data attribute is rendered in the DOM while loading th page it is not returned in the data returned for the AJAX call when inspecting console or resources.
In DOM:
<select id="shippingservices_select" name="cart[shippingservice_id]"><option value="">select a carrier</option>
<option value="7" data-price="3.9">UPS</option>
<option value="19" data-price="10.0">DHL</option>
</select>
The fetched partial:
$("#shippingservices_select").empty()
.append("<option value=\"7\">UPS<\/option><option value=\"19\">DHL<\/option>");
I am using the following ajax call:
$(document).on("change", "#lands_select", function(event){
jQuery.ajax({
url: "/carts/update_shipping/" + event.target.value,
type: "GET",
error: function (xhr, status, error) {
console.error('AJAX Error: ' + status + error);
},
success: function (response) {
console.log(response);
}
});
});
Which should return both value and data attribute, for the form:
<%= form_for :cart, :url => {:action => "show_shipping"}, :html => { :method => "get", :remote => true } do |f| %>
<%= f.collection_select(:land_id, Land.all, :id, :name, {:prompt => "select a country"}, {:id => 'lands_select'}) %><br>
<%= f.select(:shippingservice_id, options_for_select(#shippingservices.collect { |s| [s.name.titleize, s.id, {'data-price' => s.price}] }), {:prompt => "select a carrier"}, {:id => 'shippingservices_select'}) %><br>
<% end %>
Shipping: €<div id="shipping_cost"></div><br>
with the update_shipping.js.erb view:
$("#shippingservices_select").empty()
.append("<%= escape_javascript(render(:partial => #shippingservices)) %>");
and the rendered partial _shippingservice.html.erb of:
<option value="<%= shippingservice.id %>" data-price="<%= shippingservice.price %>"><%= shippingservice.name.titleize %></option>
In the controller I have:
def update_shipping
...
respond_to do |format|
format.js
end
end
Following my partial _shippingservice.html.erb I should get both value and data attribute, but I do not. I think this is the reason why:
$(document).on("change", "#shippingservices_select", function(event){
var price = $(event.target).data('price');
$("#shipping_cost").html(price);
});
Does not return the value of data-price.
How can I get this to work? Thank you in advance.
UPDATE
I was trying to troubleshoot it and things appear to be quite strange.
Both selectors display data in the DOM. The shipping service select field which get’s loaded on page load shows everything. On changing the land selector, the partial which gets loaded and which I can inspect in console does only display option value + name, but when changing the _shippingservice partial I found out that the code which gets loaded through AJAX appears not to be generated by the partial referenced in the JS Ajax call, since I can change the code inside without producing any effect on the delivered payload.
But when substituting
<%= escape_javascript(render(:partial => #shippingservices))%>
By
<%= escape_javascript(render ‘carts/shippingservice’)%>
Nothing is displayed and loaded.
You can check what partials are getting rendered on the terminal where you are running rails. Looks like you have more than 1 possible partial for shipping services.
You can also specify which partial to use instead of letting rails guess:
render partial: 'carts/shippingservice', collection: #shippingservices, as: :shippingservice

Rails Ajax Refresh Partial while persisting params

I have a Rails app with a controller/view called "calls". Here is the basic controller action for index:
calls_controller.rb
def index
if params[:region].present?
#assigned = Call.where(region_id: params[:region][:area]).assigned_calls.until_end_of_day
#unassigned = Call.where(region_id: params[:region][:area]).unassigned_calls.until_end_of_day
else
#assigned = Call.assigned_calls.until_end_of_day
#unassigned = Call.unassigned_calls.until_end_of_day
end
end
Here are my views:
index.js.erb
$('#active').html("<%= escape_javascript render :partial => 'calls/assigned_calls', :locals => {:assigned_calls => #assigned} %>");
$('#inactive').html("<%= escape_javascript render :partial => 'calls/unassigned_calls', :locals => {:unassigned_calls => #unassigned} %>");
$(".select").select2({
placeholder: "Select One",
allowClear: true
});
index.html.erb
<div id="active">
<%= render "assigned_calls" %>
</div>
<div id="inactive">
<%= render "unassigned_calls" %>
</div>
<script>
$(document).ready(function() {
setInterval(function () {
$.ajax('calls/<%= params[:region][:area] %>');
} , 5000);
});
</script>
_assigned_calls.html.erb (view code omitted)
<%= form_tag calls_path, :method => 'get' do %>
<p>
<%= select_tag "region[area]", options_from_collection_for_select(Region.order(:area), :id, :area, selected: params[:region].try(:[], :area)), prompt: "Choose Region" %>
<%= submit_tag "Select", :name => nil, :class => 'btn' %>
So what's happening is on page load if I do not have the params of :region passed it sets the calls without being scoped by region. If region_id is present then it scopes calls where region_id is "1" or whatever the Region ID is that is passed from the submit_tag.
This works fine in the controller and view, however here's my problem. My index.html.erb I need to refresh the partials WITHOUT disturbing the params passed. So on setInterval I need to figure out how to reload the partials while persisting the params passed in the URL.
I tried to figure this out using a setInterval method but I'm not sure what I'm doing here 100%.
Can someone give me some advice on how to refresh the partials every 5 seconds while persisting the params so my instance variables persist through refresh?
If you need more context and/or code please let me know.
Update
Trying to rework the javascript based off an answer from a user and here's what I have.
<script>
$(document).ready(function() {
setInterval(function () {
$.ajax({
url: 'calls_path',
type: "GET",
data: { "region": '<%= #region.html_safe %>' }
}), 5000);
});
});
</script>
The page will load but when it tried to trigger in the chrome inspector I get:
calls?utf8=✓&region[area]=3:2901 Uncaught SyntaxError: Unexpected token )
Maybe this is a JS syntax error or I'm not closing the function properly.
If I understood properly, you want to have your parameters somehow pipelined through AJAX call to your controller, back to your js.erb file where it refreshes the partials?
My advice is to set passed parameters as instance variables in your controller like this:
calls_controller.rb
def index
if params[:region].present?
#region = params[:region]
#assigned = Call.where(region_id: params[:region][:area]).assigned_calls.until_end_of_day
#unassigned = Call.where(region_id: params[:region][:area]).unassigned_calls.until_end_of_day
else
#assigned = Call.assigned_calls.until_end_of_day
#unassigned = Call.unassigned_calls.until_end_of_day
end
end
Now your #region instance variable will be available in your index.js.erb
where you can pass it to other partials you are trying to render.
index.js.erb
$('#active').html("<%= escape_javascript render :partial => 'calls/assigned_calls', :locals => { :assigned_calls => #assigned, :region => #region } %>");
$('#inactive').html("<%= escape_javascript render :partial => 'calls/unassigned_calls', :locals => { :unassigned_calls => #unassigned, :region => #region } %>");
_assigned_calls.html.erb
<%= form_tag calls_path, :method => 'get' do %>
<%= select_tag "region[area]", options_from_collection_for_select(Region.order(:area), :id, :area, selected: region.try(:[], :area)), prompt: "Choose Region" %>
<%= submit_tag "Select", :name => nil, :class => 'btn' %>
<% end %>
Also, I think that better practice in your index.html.erb script tag
is to do it like this:
<script>
$(document).ready(function() {
setInterval(function () {
$.ajax({
url: 'calls_path',
type: "GET",
data: { "region": '<%= #region.html_safe %>' }
});
}, 5000);
});
</script>
Please test this out if you're interested and get back to me :)

Calling coffeescript in rails form

I have this coffeescript for dynamic select boxes to show only those models in models select box which relate to selected makes in makes select box.
And I am gonna have multiple fields working with this function separately, so anonymous function wont work.
Coffeescripts looks like this
../assets/javascripts/diys.coffee
DynamicSelect = (makesSelect, modelsSelect) ->
$(document).on 'change', makesSelect, (evt) ->
$.ajax 'update_make_models',
type: 'GET'
dataType: 'script'
data: {
make_id: $("'makesSelect' option:selected").val()
}
error: (jqXHR, textStatus, errorThrown) ->
console.log("AJAX Error: #{textStatus}")
success: (data, textStatus, jqXHR) ->
console.log("Dynamic make select OK!")
../views/diys/update_make_models.coffee
$(modelsSelect).empty()
.append("<%= escape_javascript(render "make_models/make_model") %>")
And here's part of my form, which will repeat multiple times, only id's will change, which I will pass as arguments to "DynamicSelect" function. So where and what do I need to put to launch this function properly?
<div class="vehicle_field">
<%= f.fields_for :attached_vehicles do |av| %>
<p>Select make</p>
<%= av.select :make, options_for_select(#makes.collect { |m| [m.make_name, m.id] }), { include_blank: "Select make" }, { id: 'makes_select1' } %><br>
<p>Select model</p>
<%= av.select :model, (render "make_models/make_model"), {prompt: "Select model"}, { id: 'models_select1' } %><br>
<p>Select years</p>
<%= av.select :start_year, (Time.now.year + 1).downto(Time.now.year - 100).to_a, prompt: "Year (from)" %>
<%= av.select :end_year, (Time.now.year + 1).downto(Time.now.year - 100).to_a, prompt: "Year (to)" %><br>
<% end %>
</div>
------------------------------------------------------------------------------------------------------------------------------------
Edit, trying to accomplish dynamic select boxes with data-remote attribute as Richard Peck suggested
In console it seems that I'm getting right "make_id" in parameters when selecting make in makes select box, but I can't find the way to pass it to controllers #models variable, am I doing anything right?
Form part for selecting attached vehicles from view
<div class="vehicle_field">
<%= f.fields_for :attached_vehicles do |av| %>
<p>Select make</p>
<%= av.select :make, (#makes.collect { |m| [m.make_name, m.id] }), { include_blank: "Select make" }, { data: { remote: true, url: "update_make_models", name: "make", update: "#diy_attached_vehicles_attributes_0_model"} } %><br>
<p>Select model</p>
<%= av.collection_select :model, #models, (render "make_models/make_model"), {prompt: "Select model"} %><br>
<p>Select years</p>
<%= av.select :start_year, (Time.now.year + 1).downto(Time.now.year - 100).to_a, prompt: "Year (from)" %>
<%= av.select :end_year, (Time.now.year + 1).downto(Time.now.year - 100).to_a, prompt: "Year (to)" %><br>
<% end %>
</div>
_make_model.html.erb partial
<% #models.collect do |models| %>
<option value="<%= models.id %>"><%= models.make_model_name %></option>
<% end %>
New action in diys_controller
def new
#diy = Diy.new
#step = #diy.steps.new
#attached_vehicle = #diy.attached_vehicles.new
#step.add_images_to_steps.new
#makes = Make.all
#models = MakeModel.where("make_id = ?", params[:make])
end
Also removed both coffeescripts and edited routes
get '/diys/update_make_models', to: 'diys#new', as: 'update_make_models'
And this is what I'm getting in console after selecting make
Started GET "/diys/update_make_models?diy%5Battached_vehicles_attributes%5D%5B0%5D%5Bmake%5D=12" for ::1 at 2016-02-17 20:03:21 +0200
Processing by DiysController#new as JS
Parameters: {"diy"=>{"attached_vehicles_attributes"=>{"0"=>{"make"=>"12"}}}}
Make Load (1.0ms) SELECT "makes".* FROM "makes"
MakeModel Load (1.0ms) SELECT "make_models".* FROM "make_models" WHERE (make_id = NULL)
Rendered make_models/_make_model.html.erb (3.0ms)
Rendered diys/_form.html.erb (151.0ms)
Rendered diys/new.html.erb within layouts/application (260.0ms)
Completed 200 OK in 451ms (Views: 446.4ms | ActiveRecord: 2.0ms | Solr: 0.0ms)
You can just bind .on to the element itself:
#app/assets/javascripts/application.coffee
$(document).on "change", "select#makes_select1", (evt) ->
$.ajax 'update_make_models',
type: 'GET'
dataType: 'script'
data:
make_id: $(this).find("option:selected").val()
error: (jqXHR, textStatus, errorThrown) ->
console.log "AJAX Error: #{textStatus}"
success: (data, textStatus, jqXHR) ->
console.log "Dynamic make select OK!"
Something cool you'll want to look at is the data-remote attribute for select boxes:
= f.collection_select :attribute, #collection, :id, :name, {}, { data: {remote: true, url: "update_make_models"} }
This passes the variable params[:object][:attribute] to the data-url, which you'll be able to mange in your controller to pull back the required data you want.
Using the above code will rid you of the need for the ajax definition in your JS.

Use jQuery to pass an instance from the view to the controller

Environment Ruby 2.0.0, Rails 4.0.3, Windows 8.1, jQuery
EDIT: Just FYI, I was discussing the issue the other day and I was told that the common method to solve this problem would be just to pass the record ID. Certainly, I would recommend that solution in a general case. In this case, the record is being created and has not yet been stored, so it has no ID and cannot have one until all required fields are completed.
I need to pass the object instance from the view through jQuery to the controller so that the controller use it to render a partial using dependent selects. This process was generally working even though I was just passing a string that named the object. But, now I have to implement strong parameters to permit updates and that requires the actual instance and not just the string name of the instance.
In jQuery, I use the following to obtain the instance but it is obviously wrong because it only gets me the string name of the instance. I assume it needs to be serialized perhaps? But, I can only get the string name which cannot be serialized.
var car = $('select#car_year_id').attr("car");
The basic question is, how do I retrieve the actual instance of car within jQuery? Alternatively, I guess, the question would be that, given the string name of an instance within Ruby on Rails, how do I address the actual instance? Either one would probably suffice. Of course, other alternatives will be welcomed. Thanks.
The form is:
<div class="span8">
<%= simple_form_for #car,
defaults: {label: false},
html: {class: 'form-vertical'},
wrapper: :vertical_form,
wrapper_mappings: {
check_boxes: :vertical_radio_and_checkboxes,
radio_buttons: :vertical_radio_and_checkboxes,
file: :vertical_file_input,
boolean: :vertical_boolean
} do |f| %>
<%= f.input(:stock_number, {input_html: {form: 'new_car', car: #car}, autocomplete: :off, placeholder: 'Stock number?'}) %>
<%= f.input(:year_id, {input_html: {form: 'new_car', car: #car}, collection: Year.all.collect { |c| [c.year, c.id] }, prompt: "Year?"}) %>
<%= render partial: "makes", locals: {form: 'new_car', car: #car} %>
<%= render partial: "models", locals: {form: 'new_car', car: #car} %>
<input type="submit" form="new_car" value="Create Car" class="btn btn-default btn btn-primary">
<% end %>
</div>
The "makes" partial is:
<%= simple_form_for car,
defaults: {label: false},
remote: true do |f| %>
<% makes ||= "" %>
<% if !makes.blank? %>
<%= f.input :make_id, {input_html: {form: form, car: car}, collection: makes.collect { |s| [s.make, s.id] }, prompt: "Make?"} %>
<% else %>
<%= f.input :make_id, {input_html: {form: form, car: car}, collection: [], prompt: "Make?"} %>
<% end %>
<% end %>
The jQuery is:
$(document).ready(function () {
// when the #year field changes
$("#car_year_id").change(function () {
// make a GET call and replace the content
var year = $('select#car_year_id :selected').val();
if (year == "") year = "invalid";
var form = $('select#car_year_id').attr("form");
if (form == "") form = "invalid";
var car = $('select#car_year_id').attr("car");
if (car == "") car = "invalid";
$.post('/cars/make_list/',
{
form: form,
year: year,
car: car
},
function (data) {
$("#car_make_id").html(data);
});
return false;
});
});
The controller action is:
def make_list
makes = params[:year].blank? ? "" : Make.where(year_id: params[:year]).order(:make)
render partial: "makes", locals: { car: params[:car], form: params[:form], makes: makes }
end
I found the answer! So excited!
There is a new HTML construct that allows you to using an arbitrary attribute to an HTML element as long as the name is preceded by "data-". For example:
<%= f.input(:year_id, {input_html: {form: 'new_car', data-car: #car}}, collection: Year.all.collect { |c| [c.year, c.id] }, prompt: "Year?"}) %>
This is problematic in Rails, because Rails doesn't like hyphens in symbols. However, there is an optional helper using the data: symbol to pass a hash as in:
<%= f.input(:year_id, {input_html: {form: 'new_car', data: { car: #car}}, collection: Year.all.collect { |c| [c.year, c.id] }, prompt: "Year?"}) %>
See: Best way to use html5 data attributes with rails content_tag helper?
Then, within JavaScript, you can use the dataset property to retrieve a DOMStringMap object as follows:
var element = document.getElementById('car_year_id');
var car = element.dataset.car;
See: HTML5 Custom Data Attributes (data-*)
This returns car as a hash object, which is really just what I needed!
Overall reference that helped a lot: Rails 3 Remote Links and Forms: A Definitive Guide
Just for completeness, I used to following code to convert the hash into an object back in the controller:
car_hash = params[:car].gsub!(/":/, '" => ')
null = nil
#car = Car.new(eval(car_hash))

Autocomplete with typeahead in rails 3

I'm developing autocomplete for a particular form in my rails app; for this purpose I'm using typeahead.js with custom controller method. So far, it works but I need using those values within the form again so that I can press the submit button and the form will be posted and processed by rails normally. How can I do this? Here's the code right now
.page-header
%h1
= #org.name
%small= t('.title')
= form_for #org_admin, |
url: organization_organization_admins_path(#organization) do |f|
.form-group
= f.label t('.user')
= f.hidden_field :user, id: 'user_id'
%input.typeahead{ :type => "text", :autocomplete => "off"}
= f.submit t('.submit'), class: 'btn btn-primary'
= link_to t('.back'), organization_organization_admins_path(#organization)
:javascript
$(document).ready(function() {
$('input.typeahead').typeahead({
name: 'names',
remote: "#{search_organization_organization_admins_path(#organization)}?term=%QUERY",
engine: Hogan,
template: '<p><strong>{{username}}</strong></p>',
limit: 10
}).on('typeahead:selected', function(e, data){
$('#user_id').value = data.id
});
});
So, I would like to populate the :user attribute in the form with the json object returned by the controller
Nevermind, I figured out... the above code is in the right path, except for the call to this line
$('#user_id').value = data.id
Since I'm using jQuery to select the hidden element I had to use the jQuery val function instead
$('#user_id').val(data.id)

Categories