Reputation: 11
I want to implement model based dropdowns where in when value for one dropdown(model) is selected it serves the basis for selection to be in second dropdown.
And hence I am requiring the id from first dropdown to be used to apply filter for second dropdown.
<div class="study-search">
<%= form_with(url: show_subjects_path(:site_id), method: :get, html: {style: 'font-size:16px'}) do |f| %>
<%= f.label 'Select Study' %>
<%= f.select :id, Study.all.collect {|study| [study.title, study.id] }, {:include_blank => "--Choose--"}%>
<%= f.label 'Select Site' %>
<%= f.select :site_id, Site.where(study_id: :id).collect {|site| [site.name, site.id] }, {:include_blank => "--Choose--"}%>
<div class="btn btn-sm", style="display: inline">
<%= f.submit "GO" %>
</div>
<% end %>
</div>
the :id used in the below snippet is not the correct way. Please suggest the proper way to do so.
<%= f.select :site_id, Site.where(study_id: :id).collect {|site| [site.name, site.id] }, {:include_blank => "--Choose--"}%>
Upvotes: 1
Views: 1705
Reputation: 181
There are multiple ways to solve this with different levels of complexity. I'll start from the least complex (but probably clunky) to the most complex
The first two options will do a full page refresh which isn't as bad as it sounds, specially if you're using Turbo Drive (formerly known as Turbolinks).
The caveat with these two options is that the navigation will always be a Turbo
visit
navigation meaning that each time, you'll create a new page in the navigation stack. You can overcome this by overriding your form submission with Stimulus and making a TurboGET
request with areplace
action. See the Turbo Drive docs for more on this.
What you have there can already achieve what you expect if the user presses the Go
button when they change the first select box. You'll just have to change the Site
select to filter by the params sent by the form:
<%= f.select :site_id, Site.where(study_id: params[:site_id]).collect {|site| [site.name, site.id] }, {:include_blank => "--Choose--"}%>
After the page renders again you'll have a new select box with the filtered data.
This approach is very clunky but it set's a great foundation for you to optimize it.
change
Now if you add some Javascript, you'll be able to detect when the first select
changes and submit the form when it does. For this, I suggest you use Stimulus JS
Check out the docs to learn more about Stimulus if you haven't used it to be able to follow the following
select
changesimport { Controller } from "stimulus"
export default class extends Controller {
submit() {
// add some code that submits a form. An easy way to do it is to use Rails UJS to do it but it's no longer recommended by the community since it'll be deprecated soon.
Rails.fire(this.element, 'submit');
}
}
Note that the controller
and action
keywords are added to the form_with
method and the first f.select
method.
<div class="study-search">
<%= form_with(url: show_subjects_path(:site_id), method: :get, data: {controller: 'form'}, html: {style: 'font-size:16px'}) do |f| %>
<%= f.label 'Select Study' %>
<%= f.select :id, Study.all.collect {|study| [study.title, study.id] }, {:include_blank => "--Choose--"}, { data: {action: 'form#submit' }%>
<%= f.label 'Select Site' %>
<%= f.select :site_id, Site.where(study_id: params[:site_id]).collect {|site| [site.name, site.id] }, {:include_blank => "--Choose--"}%>
<div class="btn btn-sm", style="display: inline">
<%= f.submit "GO" %>
</div>
<% end %>
</div>
This will essentially mimic the user clicking the Go
button whenever the first select changes
If you're in the latest version of rails and you've included the hotwire-rails
gem, you'll be able to leverage all the new technologies coming to Rails and the HTML over the wire techniques that come with it.
The newest versions of turbo now handle forms (as opposed to Turbolinks). Please refer to the Turbo Frames documentation for techniques on how to do this. With a turbo frame you're essentially doing the same thing as with options 1 and 2 which is sending a GET request and getting a full page back as a response BUT with the difference that with Turbo frames you'll be able to selectively replace parts of your DOM.
I'm not an expert yet on turbo frames so I won't post an exact example.
This is the most complex option but is still relatively simple as far as your environment is setup with Action Cable. What Turbo Stream does is allow you to target an update to your DOM with 5 basic dom actions: append
, prepend
, replace
, update
and remove
. These operations will be streamed back to the front end from the server using a web socket connection.
In your case you probably want to replace
or update
the select with the new list of options.
I'm not an expert yet on turbo streams so I won't post an exact example.
From StimulusReflex's site:
[Stimulus Reflex is] A new way to craft modern, reactive web interfaces with Ruby on Rails. We extend the capabilities of both Rails and Stimulus by intercepting user interactions and passing them to Rails over real-time websockets. These interactions are processed by Reflex actions that change application state. The current page is quickly re-rendered and the changes are sent to the client using CableReady. The page is then morphed to reflect the new application state. This entire round-trip allows us to update the UI in 20-30ms without flicker or expensive page loads.
With Stimulus Reflex you get a very seamless experience to do these kinds of interactive pages but it requires an extra dependency. It's definitely worth a try but it's also a new dependency you'll bring in your code base (assuming you adopt the new Rails defaults coming in version 7 which will include Hotwire)
Using Stimulus JS you can potentially load all options from all sites when the page loads and filter using just javascript. It's doable but it's definitely more complex because you'll need to write a lot more javascript and your initial page load might be slow if you have too many sites to search from. I would not recommend this approach because it might add unnecessary complexity to your app but it is an option.
Regardless of what you do, if you want for the form to be submitted when the user changes something on the front-end (like changing the value of a select field or typing something into a text field, or hovering or any other action that can be tracked in the front-end) you need to write a bit of javascript. I strongly suggest you check out StimulusJS
which is part of Hotwire
, the newest addition to the rails ecosystem for the front end which will be the default way of building your apps with the upcoming Rails 7 release.
I hope this gives you some pointers.
Go Rails has awesome resources on the subject https://www.youtube.com/results?search_query=go+rails+stimulus
https://www.youtube.com/watch?v=AdktV7r2BQk
https://www.youtube.com/watch?v=Q7uOPVfZ3Go
Upvotes: 6