I have a simple_form input field that is part of a form that I need to update asynchronously when the onchange event is triggered.
The field that I need to update is displayed as a select box since it is a collection. But the collection represents records of a nested set model. So when the user selects a particular value, I want to be able to update the same field with its children and give the user the option of picking any of the children as the value for the same field.
The problem is, how do I update just that field without touching the rest of the form.
Just to give some idea of my current setup: The form looks something like this:
<%= simple_form_for @product, html: {class: 'form-horizontal'} do |f| %>
<%= f.input :product_name %>
<%= f.association :location, collection: @locations, input_html: { data: {remote: :true, url: "/update_locations"} } %>
<%= f.submit 'Post', class: 'btn btn-default btn-lg' %>
<% end %>
@product is a new Product which belongs_to Location, @locations is a collection of Location each of which has_many Product hence the use of the simple_form association method Location also acts_as_nested_set
I have this in my routes.rb
get 'update_locations' => 'products#update_locations'
I need help in completing the controller action:
def update_locations
@product = Product.new
@location = Location.find_by_id(params[:product][:location_id])
@locations = @location.children
# TODO: render code that updates the simple_form field with
# the new @locations collection
end
I am not sure what code should go in the partial view that the controller above is supposed to render.
The problem was I didn't know how to update just one element inside a simple_form. I also had a very loose grasp on using AJAX with rails not knowing you could have both HTML and JS partial views for the same controller, as such I was trying to fit all my code in just HTML which would have been cumbersome and going against unobtrusive JavaScript.
Stumbling upon the jQuery method replaceWith was also very helpful.
Armed with this knowledge, I was able to accomplish what I had set out to do.
I created a JavaScript partial view which is rendered in response to the AJAX call. The partial renders a .html.erb partial view which rebuilt a simple_form with the updated element. The JS view then extracts the updated element from the rebuilt form and uses the jQuery replaceWith() method to replace just the specific element inside the initial form while leaving the rest of the form unchanged.
Here is how the JS partial (location.js.erb) looks like:
var updatedLocations = $('#product_location_id', $('<%= j render "updateLocations" %>'))
$('#product_location_id').replaceWith(updatedLocations)
And the HTML erb partial (updateLocations.html.erb) looks something like this:
<%= simple_form_for @product, html: {class: 'form-horizontal'} do |f| %>
<%= f.association :location, collection: @locations, input_html: { data: {update_locations: "true", remote: :true, url: "/update_locations"} } %>
<% end %>
And the controller:
def update_locations
@product = Product.new
@location = Location.find_by_id(params[:product][:location_id])
@locations = @location.children
if @locations.empty?
render nothing: true
else
render partial: 'locations'
end
end