simple_form select collection populated by AJAX - ruby-on-rails

I thought this would be fairly easy, but I'm not finding any help by Googling.
I have a form (simple_form) with numerous inputs with select lists (collections) that are populated from the database, so many it is slowing down the initial page load. I thought I could speed it up by only populating those drop down lists as the user selects them using Ajax. Is there something built in like remote => true for the form itself? Can someone point me in the right direction?
EDIT:
I found this SO question but I cannot figure out how to implement the answer.
Currently, my form looks like this;
= simple_form_for(#account)
= f.input :account_number
= f.input :area, collection: #areas
= f.submit nil, :class => 'btn btn-primary'
Based on the answer in the linked question, I should add something like this, but of course it is not working
= simple_form_for(#account)
= f.input :account_number
= f.input :area, collection: #areas, :input_html => {"data-remote" => true, "data-url" => "/my_areas", "data-type" => :json}
= f.submit nil, :class => 'btn btn-primary'

I can think of two ways to go about this if you don't want to load the contents initially when the page loads. One way is to run a script after the DOM has loaded to change the options for the select tag and the other is to collect the options when you click on the drop-down on the select element. I might go for the first way because there wouldn't be latency when a user clicks on the select element--they wouldn't have to wait for the options to populate.
So you'd run a jQuery script on document ready that makes an AJAX call to a method in your controller, which then returns the collections you want, then you iterate through the select elements you want to change with JQuery scripts. It might look something like this.
# in view with the select options to be changed
$(document).ready(function() {
$.get(change_selects_path, function(response) {
$.each(response, function(args) {
// code for each select element to be changed
$('.class_of_select_element').html(<%= j options_from_collection_for_select(args) %>);
});
});
)};
# in controller
def change_selects
# make db calls and store in variables to feed to $.get request
end
Note that this not tested but should give you a good start towards a solution. For further info on the each loop, you can check out this documentation.

Not sure if this fits your exact use case (please clarify if not), but I also have a few collection selects that have a large amount of database rows behind them. I use the select2-rails gem to take care of this. Users can begin to type in the name and the relevant results will show up (it will also show a few initially if they don't type something).
Check it out here: https://github.com/argerim/select2-rails
Edit: For a cascading dropdown, I recommend this gem: https://github.com/ryanb/nested_form

Related

Cocoon + Rails—how to access object data on form error (for dynamic fields)

I have cocoon creating association sub-forms, one of these forms has projects and codes
Setup
/ view (in slim format)
.nested-fields.subform.project-subform
= link_to_remove_association "×", f
#project-code-container
.form_group
= f.label :project_id, "Project"
= f.select(:project_id,
#current_projects.map { |project| [project.name, project.id] },
{},
{ onChange: "updateCodes(this)" },
)
.form_group
= f.label :project_code_id, "Code"
= f.select(:project_code_id, #current_project_codes[1])
The second select should show a list of codes based on the index of the selected project. So if project 3 was selected, then the options for codes should be #current_project_codes[3]
Problem
When I hit a validation error on submit, the form is reloaded with the previous data already filled out (like a normal rails form). However, I don't know how to tell the project_code select which options it should load. Since this is a cocoon form, I don't have an object like #project to access the data.
I need to figure out what project was selected, so I can show the right codes.
On validation error/reload, how can I get a cocoon object's data?
p.s. I'm struggling to define this question, if you have ideas on how to make my question clearer, please let me know.
Credit to #arieljuod
f.object gives the form's object with its data
For my specific problem, this was the line that allowed a select to autopopulate with any pre-saved data:
f.select(
:project_code_id,
#current_project_codes[f.object.project_id || #current_projects.first.id]
)

How get the value of a collection_select within the same html.erb form itself

I have a form with this collection_select
<%= collection_select :bmp, :bmpsublist_id,
Bmpsublist.where(:bmplist_id => #bmp.bmp_id), :id,
:name,{ :required => false,
:selected => #bmp.bmpsublist_id, } %>
I would like to be able to get the value of this collection_select so that lower down in the same form, I can check to see which list I should use when displaying another collection_select
Something like this partial pseudocode here:
if earlier result == 2 then
use this list: Irrigation.where(:id != 8)
else
use this other list: Irrigation.all
and they would be updating the collection_select:
<%= collection_select :bmp, :irrigation_id, the_chosen_list_from_above, :id, :name,
{:prompt => 'Select Irrigation Type'}, {:required => true} %>
How can I do that?
Based on what you've asked, there are two ways to query and apply the value of the collection: static and dynamic.
Static occurs at the time that the ERB view is rendered, and this will apply the logic at the time that the page is initially rendered and loaded. Dynamic occurs after the page is loaded, and as the user interacts with the elements on the page. Which approach you choose to go with depends entirely on your application's design and intended level of interaction with the user.
Static Detection
You're already specifying the selected item in the initial collection_select, so you can reuse that in your later code. Try this, based on your pseudocode example:
<% if #bmp.bmpsublist_id == 2 %>
<% irrigation_list = ["Sprinkle", "Furrow/Flood", "Drip", "Furrow Diking"] %>
<% else %>
<% irrigation_list = ["Sprinkle", "Furrow/Flood", "Drip", "Furrow Diking", "Pads and Pipes - Tailwater Irrigation"] %>
<% end %>
<%= select :bmp, :irrigation_id, options_for_select(irrigation_list),
{ :prompt => 'Select Irrigation Type'}, { :required => true } %>
Why will this work? The :selected option for the initial collection_select is where you provide which option will be initially chosen. Since this value is typically taken from the model value, it's supplied in a separate param from the actual collection values. So, it's queued up and ready for you, simply by virtue of sticking to the Rails conventions.
The subsequent select builds the HTML <select> element and uses the options_for_select to turn the array of options into HTML <option> elements. This way, you can use the variable list of options to select from, based on which element from the original collection_select was chosen.
Best thing of all: with the static approach, you don't have to drop into Javascript (or jQuery) to do this; it gets rendered directly by the ERB template (or the HAML template, if that's your bag).
Dynamic Detection
If you actually wanted dynamic behavior, you can drop into Javascript / jQuery and get it done. You can create your "Irrigation Types" select just like with the static approach (above), except that you initialize it with all of the options, like this:
<%= select :bmp, :irrigation_id,
options_for_select(["Sprinkle", "Furrow/Flood", "Drip", "Furrow Diking", "Pads and Pipes - Tailwater Irrigation"]),
{ :prompt => 'Select Irrigation Type'}, { :required => true } %>
Then, edit the Javascript source associated with your view (let's call it Product). Open the app/assets/javascripts/product.js (if you use CoffeeScript, it's the product.coffee file in the same directory).
Edit that Javascript file to include this code:
function OnProductEditForm() {
// Edit the selectors to match the actual generated "id" for the collections
var bmp_collection = $("#product_bmp");
var drip_collection = $("#product_irrigation_type");
var drip_option = drip_collection.find("option")[2];
function select_available_drip_options() {
var value = bmp_collection.val();
if (value == 2) {
drip_option.attr("disabled", "disabled");
} else {
drip_option.removeAttr("disabled");
}
}
bmp_collection.change(function() {
select_available_drip_options();
});
select_available_drip_options();
}
This identifies the HTML element of the collection and installs a change event handler. You'll need to verify the collection element's id, as per the code comment, and the rest happens from there. When the collection is changed (a new value is chosen), the event handler will hide or show the third select <option> (specified as find("option")[2]), as appropriate for the #product_bmp selection.
Next, in the app/views/products/_form.html.erb, include this at the end of the file:
<script>
jQuery(document).ready(OnProductEditForm);
// Uncomment the next 2 lines for TurboLinks page refreshing
//jQuery(document).on('page:load', OnProductEditForm);
//jQuery(document).on('page:restore', OnProductEditForm);
</script>
This will automatically load the OnProductEditForm method when the page loads, and will result in the afore-mentioned event handler getting installed. Note that the last 2 lines are necessary if you have TurboLinks enabled, as TurboLinks initiates events for page loading independently of the standard $(document).ready.
And that's all there is to it. Adding dynamic behavior is just that easy!
You're gonna have to use some javascript (jquery and ajax, I would suggest). When the value of the first select change (jquery), it requests (ajax) the collection (passing the current value selected) to a controller action that returns the collection that should be used. With the collection returned, you populate the options (jquery) for the second select. It's not quite simple, but if you ever did something like that, you shouldn't have problems. If never did, do some research about it... it's quite useful and improve user experience a lot!

How to assign a variable the current output of a select tag

I'm having some trouble with a select tag.
I want to assign a value to a variable depending on which name is shown on the select tag:
<%= select('product', 'name' , #products.map { |s| [s.name, s.id] }, {}, {:class => "form-control"}) %>
The values display correctly, but I want to assign the currently selected value to a variable so that it can be used later on the same page to display content with the relevant details of the selected product.
For example:
#product = currently_displayed_name
Is this possible with Rails and how to do it? Or do I might need to use another resource?
Props to the comments, the answer is that Rails does not give you a way to do that. Depending on what you need to manipulate when user changes select value, you might be able to find a JavaScript library for it.
Otherwise, you can load the default variable on the page, and then handle the rest with an onchange handler:
$('#product_name').change(function() {
// perform actions on the page with javascript
});

how to ensure rails multiselect input field passes all values correctly?

so, I've got a form inside a bootstrap modal.
when the modal loads, some javascript calls to the server to figure out what the current user has already chosen (this is an edit modal). once the server response returns, the form select sets itself to the values that the user has already chosen as is demonstrated in the below screenshot.
My problem occurs when the form is submitted. The params being sent to the server have the key 'room_object_id' that points to a single value. Naming issues of that key aside, I need that key to point to an array of values or a string or any structure that will hold multiple values and this is not the case, as demonstrated by the below screenshot.
My form is being generated by
<%= form_for :student, url: student_path, html: {class: 'form-horizontal sync'}, method: :put do |f| %>
<%= f.select(:room_object_id, options_for_select(#rooms.map {|room| [ room['name'], room['objectId'] ]}),{multiple: 'true', include_blank: ''} , {:class => 'select2-init form-control force-full-width', required: 'true', multiple: 'true', name: 'room_object_id'}) %>
<% end %>
I'm a bit unclear how select2 and the html form are working together and I have a feeling my problems are related to this. I'm also a bit unclear of the different usages of rails select, collection_select and options_for_select. I've looked at other SO posts on this topic but nothing pointed me in the right direction. Any pointers would be much appreciated.
Are you using the JavaScript required to run select2?
$(document).ready(function() {
$("#id_of_your_select_field").select2();
});
If you are, is your JavaScript console displaying any errors?

Query in Dynamic Select Menu

I have two models Employee , EmployeeDepartment. I made a dynamic select menu so when I choose a department it will filter employees depending on that department using the following code by simple_form
<%= f.input :employee_id , collection: #departments, as: :grouped_select, group_method: :employees, include_blank: false %>
Note: #departments = EmployeeDepartment.order(:name)
What I want to do is:
Before having dynamic selection I used this code in order to fetch specific employees
<%= f.association :employee, :collection => #supervisors_list, :label_method => :full_name, :value_method => :id, include_blank: false %>
Note that: #supervisors_list = Employee.where('guidance_supervisor' => true).order(:first_name)
guidance_supervisors.js.coffee
jQuery ->
$('#guidance_supervisor_employee_id').parent().hide()
employee = $('#guidance_supervisor_employee_id').html()
$('#guidance_supervisor_employee_department_id').change ->
department = $('#guidance_supervisor_employee_department_id :selected').text()
escaped_department = department.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/#])/g, '\\$1')
options = $(employee).filter("optgroup[label='#{escaped_department}']").html()
if options
$('#guidance_supervisor_employee_id').html(options)
$('#guidance_supervisor_employee_id').parent().show()
else
$('#guidance_supervisor_employee_id').empty()
$('#guidance_supervisor_employee_id').parent().hide()
After adding the dynamic select I am not able to use #supervisors_list which makes the menu fetch all employees. I need to be able to use #supervisors_list again, is there any way to achieve that ?
You need to use something like Gon. It has a watch feature that can help reload data without reloading page see here
Te problem with your code is only that it filters the page without reloading it. So basically being the #supervisors_list built server side you can:
use an ajax method to get the new filtered #supervisor_list from the server (gon watch or directly use jQuery .ajax)
add the #supervisor_list data to your page javascript and filter it after department filtering (also through gon or read this railscast)
There is a simple way to implement the same.
https://gist.github.com/romansklenar/3077281
This may help you to fix the problem.

Resources