So I have the following form using cocoon nesting some fields in a partial
<%= form_with(model: tournament, local: true, class: "mt-8 md:mt-12") do |f| %>
<% if tournament.errors.any? %>
<div id="error_explanation" class="bg-red-100 text-red-700 rounded-md shadow-sm p-8">
<h2 class="font-bold text-base"><%= pluralize(tournament.errors.count, "error") %> prohibited this tournament from being saved:</h2>
<ul>
<% tournament.errors.full_messages.each do |message| %>
<li class="mt-1 font-semibold text-sm"><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
....
<div class="text-xl font-black mt-8">
Tournament Standings
<div class="border-2 border-indigo-600 bg-indigo-600 w-1/6 md:w-10 mt-1"></div>
</div>
<%= f.fields_for :tournament_standings do |tournament_standing| %>
<%= render 'tournament_standing_fields', f: tournament_standing %>
<% end %>
<div class="mt-4 border-t border-gray-200 pt-5">
<span class="mt-4 inline-flex rounded-md shadow-sm">
<%= f.submit class: "mr-4 inline-flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out" %>
<%= link_to_add_association 'Add tournament standings', f, :tournament_standings, class: "inline-flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out mr-2" %>
</span>
</div>
<% end %>
Within that render I have the following
<div class="mt-4">
<%= f.label "Select tournament game", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1 rounded-md shadow-sm">
<%= f.select(:category_id, Category.all.map{|c| [c.name, c.id]}, {prompt: true}, { class: "ts_select block form-select w-full transition duration-150 ease-in-out sm:text-sm sm" }) %>
</div>
</div>
<div class="nested-fields">
<div class="mt-4">
<%= f.label :team, class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1 rounded-md shadow-sm">
<%= f.select(:team_id, Team.all.map{|t| [t.name, t.id]}, {prompt: "Select a team"}, { class: "block form-select w-full transition duration-150 ease-in-out sm:text-sm" }) %>
</div>
</div>
<div class="flex flex-col md:flex-row">
<div class="mt-4 md:mr-4">
<div class="mt-1">
<div class="flex rounded-md shadow-sm">
<span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm">
Position
</span>
<%= f.number_field :position, class: "flex-1 form-input block w-full rounded-none rounded-r-md transition duration-150 ease-in-out sm:text-sm sm" %>
</div>
</div>
</div>
<%= render partial: "tournaments/fpartials/default", tournament: @tournament, locals: { f: f } %>
<%= render partial: "tournaments/fpartials/rocket_league_fields", tournament: @tournament, locals: { f: f } %>
<div class="mt-4 md:mr-4">
<div class="mt-1">
<div class="flex rounded-md shadow-sm">
<span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm">
Assists
</span>
<%= f.number_field :assists, class: "flex-1 form-input block w-full rounded-none rounded-r-md transition duration-150 ease-in-out sm:text-sm sm" %>
</div>
</div>
</div>
<div class="mt-4">
<div class="mt-1">
<div class="flex rounded-md shadow-sm">
<span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm">
Prize $
</span>
<%= f.number_field :prize, class: "flex-1 form-input block w-full rounded-none rounded-r-md transition duration-150 ease-in-out sm:text-sm sm" %>
</div>
</div>
</div>
</div>
<%= link_to_remove_association 'Delete tournament standing', f, class: "mt-4 inline-flex justify-center text-sm font-medium" %>
</div>
Which work, as you can see I have two render partials which I want to be able to show these fields based on the category selected category_id
default should be the default shown fields and the rocket league fields hidden, however, let's say the user selects the category of rocket league
then the default fields need to hide and the rocket league fields show.
Any help with this would be great getting it to work within a partial.
There's two ways of going about it:
If you've got a ton of possible partials or they're really large, option 2 is preferable. But if you're only talking about toggling between two things option 1 is more straightforward.
For option 1:
Wrap your partials in <div>
s with distinct IDs so your Javascript can access them. In your code above you don't appear to initially set a value on the select box, so presumably you'd start with the 'default' set showing:
<div id="default-fields">
<%= render partial: "tournaments/fpartials/default", tournament: @tournament, locals: { f: f } %>
</div>
<div id="rocket-fields" hidden>
<%= render partial: "tournaments/fpartials/rocket_league_fields", tournament: @tournament, locals: { f: f } %>
</div>
(You can also of course set the 'hidden' conditionally at render time as well.)
Add a Javascript handler that fires whenever the selected value changes. The 'unobtrusive' way to do that is by adding a Javascript event listener into your page's Javascript bundle (usually in app/assets/javascript
). Something like:
document.addEventListener('turbolinks:load', () => {
const element = document.getElementById('tournament[category_id]');
if (element) {
$('#tournament[category_id]').change((e) => {
if (e.target.value == 'whatever_rocket_id_is') {
('#default-fields').hide();
('#rocket-fields').show();
}
else {
('#default-fields').show();
('#rocket-fields').hide();
}
});
}
});
One note: Because you want to have a tight coupling between a category and the fields shown, you shouldn't use the record ID as the key in the select options; it can change and you don't want to be hardcoding record IDs in your Javascript. Your best bet is probably to use the category name for both the key and the value. It's probably worth also reconsidering that tight coupling in the first place.