How to create a form with dependent fields? (RoR) - ruby-on-rails

I am wondering how to create a form that displays field dependent on the fields selected beforehand. For example: if Coffee is selected as a drink, only then do I want to allow sugar and milk amounts to be input.
Here is my form:
<%= simple_form_for(#order) do |f| %>
<%= f.error_notification %>
<div class="field">
Drink: <%= f.select :drink, ['Coffee', 'Orange Juice', 'Tea'], id: 'extended_list' %><br />
</div>
Coffee Type: <%= f.select :coffee_type, ['Espresso', 'Cappuccino', 'Filter', 'Greek', 'Frappe', 'Latte'] %><br />
Sugar Amount: <%= f.select :sugar_amount, ((1..5).map {|i| [i,i] } << ["None",nil]) %><br />
Milk Amount: <%= f.select :milk_amount, ((1..5).map {|i| [i,i] } << ["None",nil]) %><br />
<% end %>
<%= f.button :submit, class: "button is-info" %>
<% end %>

First you need to wrap the fields you'd like to hide in a div. Add a distinct id to each of them.
For example, if you want to hide coffee_type, you can add the id "js-coffee-type" to it.
Also, add a class (for instance, d-none, as used by Bootstrap) so we can hide it using CSS.
<%= simple_form_for(#order) do |f| %>
<div class="field">
Drink: <%= f.select :drink, ['Coffee', 'Orange Juice', 'Tea'], id: 'extended_list' %><br />
</div>
<div id="js-coffee-type" class="d-none">Coffee Type: <%= f.select :coffee_type, ['Espresso', 'Cappuccino', 'Filter', 'Greek', 'Frappe', 'Latte'] %></div>
<div>Sugar Amount: <%= f.select :sugar_amount, ((1..5).map {|i| [i,i] } << ["None",nil]) %></div>
<div>Milk Amount: <%= f.select :milk_amount, ((1..5).map {|i| [i,i] } << ["None",nil]) %></div>
<%= f.button :submit, class: "button is-info" %>
<% end %>
In the same file, you can write the CSS code that hides this field.
<style>
.d-none { display: none; }
</style>
Now you can write the JS code to toggle the class depending on the first field.
<script>
$("#extended_list").change(function() {
var drink = $("#extended_list").val();
if (drink == "Coffee") {
$("#js-coffee-type").removeClass("d-none");
} else {
$("#js-coffee-type").addClass("d-none");
}
});
</script>
These are not best practices but they should do the work.

Related

Displaying different divs based on dropdown selection in Rails form

I am trying to create a form in Rails that includes a dropdown menu with three options: 'foo', 'bar', and 'baz'. If the user selects one of these options, I want to display a different div based on the selected option.
Here is my code:
<% options = [['Foo', 'foo'], ['Bar', 'bar'], ['Baz', 'baz']] %>
<%= form.label :type %>
<%= form.select :type, options_for_select(options, #selected_type), name: 'type' %>
<% if #selected_type == 'foo' %>
<div>
<%= form.hidden_field :type, value:'foo' %>
</div>
<% elsif #selected_type == 'bar' %>
<div>
<%= form.hidden_field :type, value:'bar' %>
</div>
<% elsif #selected_type == 'baz' %>
<div>
<%= form.hidden_field :type, value:'baz' %>
</div>
<% end %>
This is in my controller:
#vendor_item = VendorItem.new
#selected_type = params[:type]
puts "Selected type: #{#selected_type}"
I have implemented this using a select element, but the form is not working as expected. When I select an option from the dropdown menu, the form is not being submitted, and the divs are not being displayed.
CASE 1: If the form should be submitted on the server side, after the dropdown change:
<% options = [['Foo', 'foo'], ['Bar', 'bar'], ['Baz', 'baz']] %>
<%= form_with url: "your URL goes here", class: 'test_submit_form' do |form| %>
<%= form.label :type %>
<%= form.select :type, options_for_select(options, #selected_type), name: 'type' %>
<% if #selected_type == 'foo' %>
<div>
<%= form.hidden_field :type, value:'foo' %>
</div>
<% elsif #selected_type == 'bar' %>
<div>
<%= form.hidden_field :type, value:'bar' %>
</div>
<% elsif #selected_type == 'baz' %>
<div>
<%= form.hidden_field :type, value:'baz' %>
</div>
<% end %>
<%= form.submit %>
<% end %>
Javascript:
<script>
$('#type').on('change', function(){
$('form.test_submit_form').submit();
});
</script>
CASE 2: If you just need to show/hide DIVs on client side, after dropdown change:
<% options = [['Foo', 'foo'], ['Bar', 'bar'], ['Baz', 'baz']] %>
<%= form_with url: "your URL goes here", class: 'test_submit_form' do |form| %>
<%= form.label :type %>
<%= form.select :type, options_for_select(options, #selected_type), name: 'type' %>
<div class="option-container foo" style="display: <%= #selected_type == 'foo' ? 'block' : 'none'%>">
<%= form.hidden_field :type, value:'foo' %>
</div>
<div class="option-container bar" style="display: <%= #selected_type == 'bar' ? 'block' : 'none'%>">
<%= form.hidden_field :type, value:'bar' %>
</div>
<div class="option-container baz" style="display: <%= #selected_type == 'baz' ? 'block' : 'none'%>">
<%= form.hidden_field :type, value:'baz' %>
</div>
<%= form.submit %>
<% end %>
Javascript:
<script>
$('#type').on('change', function(){
$('.option-container').hide();
$('.option-container.' + $(this).val()).show();
});
</script>
Notice the option-container and option (foo, bar, baz) class on div and controller code will remain the same for both cases:
#selected_type = params[:type]

New search form partial is redirecting to my main index page for the model

I have an events model and I have an address field where I have some geolocation logic being implemented. On my main index page, I'm rendering a search form partial that works just fine to be all to search all of the events in my database.
Here's the original search form:
<%= search_form_for #q,
data: { controller: "places", action: "google-maps-callback#window->places#initMap" } do |f| %>
<div class="formlook">
<div class="event-search-container">
<div class="event-name">
<%= f.label "Event name", class: "form-label" %>
<%= f.search_field :name_cont, placeholder: "Any event", class: "form-control" %>
</div>
<div class="geolocation">
<%= f.label "Geolocation", class: "form-label" %>
<%= f.search_field :address_or_city_or_state_or_country_or_continent_cont_all, class: "form-control", placeholder: "Search by continent, country, state, or city", data: { places_target: "field" } %>
</div>
<div class="dancestyle">
<%= f.label "Dance style", class: "form-label" %>
<%= f.select(:dance_styles_id_in, DanceStyle.all.pluck(:name, :id), { :include_blank => "All dance styles" }, { class: "form-select", id: "select-dancestyle", multiple: true, placeholder: "Any dance style" }) %>
</div>
<div class="eventtype">
<%= f.label "Event Type", class: "form-label" %>
<%= f.select :event_type_id_in, EventType.all.pluck(:name, :id), { :include_blank => "All event types" }, { id: "select-artist", multiple: true, placeholder: "Any event type", class: "form-select" } %>
</div>
<div class="month">
<%= f.label "Month", class: "form-label" %>
<%= f.select(:event_month_in, (Date::MONTHNAMES[1..12]), { prompt: "Any month" }, { id: "select-month", :include_blank => "Any month", class: "form-select", multiple: true }) %>
</div>
<div class="year">
<%= f.label "Year", class: "form-label" %>
<%= f.select :event_year_in, Date.today.year-2 .. Date.today.year+3, { prompt: "Any year" }, { id: "select-year", include_blank: true, class: "form-select", multiple: true } %>
<br>
</div>
</div>
</div>
<%= f.submit "Search!", class: "button-orange" %>
<% end %>
I'm now creating specific event index pages for particular cities now. So I want to be able to use the other search parameters from my main index page without the name or the address field. So I created a new html erb file with a new scope based off the event model for a particular city:
<div class="container index">
<div>
<h1>Dance Events in Austin, Texas!</h1>
<br>
<div class="help-attn">
<p>All of the search options work together! Create your own search filters to find the events you want!</p>
</div>
<%= render 'events/local_search_form' %>
<br>
<h5>Currently displaying <%= pluralize(#events.in_austin.count, "event") %>. </h5>
<br>
</div>
<h3>Upcoming Events</h3>
<div class="event-list-wrapper">
<% #events.upcoming_events.in_austin.each do |event| %>
<%= render 'event', event: event %>
<% end %>
</div>
<h3>Past Events</h3>
<div class="event-list-wrapper past-event">
<% #events.past_events.in_austin.each do |event| %>
<%= render 'event', event: event %>
<% end %>
</div>
I also needed to add the def atx_index to my event model with the same parameters as the def index since in my head they are both index pages, just one has a different scope.
From above you will see that I created a different search form partial to reflect the two fields I don't need anymore, here's the new search form partial.
<%= search_form_for #q do |f| %>
<div class="formlook">
<div class="local-search-container">
z <%= f.hidden_field :address_or_city_or_state_or_country_or_continent_cont_all, value: [""] %>
<div class="dancestyle">
<%= f.label "Dance style", class: "form-label" %>
<%= f.select(:dance_styles_id_in, DanceStyle.all.pluck(:name, :id), { :include_blank => "All dance styles" }, { class: "form-select", id: "select-dancestyle", multiple: true, placeholder: "Any dance style" }) %>
</div>
<div class="eventtype">
<%= f.label "Event Type", class: "form-label" %>
<%= f.select :event_type_id_in, EventType.all.pluck(:name, :id), { :include_blank => "All event types" }, { id: "select-artist", multiple: true, placeholder: "Any event type", class: "form-select" } %>
</div>
<div class="month">
<%= f.label "Month", class: "form-label" %>
<%= f.select(:event_month_in, (Date::MONTHNAMES[1..12]), { prompt: "Any month" }, { id: "select-month", :include_blank => "Any month", class: "form-select", multiple: true }) %>
</div>
<div class="year">
<%= f.label "Year", class: "form-label" %>
<%= f.select :event_year_in, Date.today.year-2 .. Date.today.year+3, { prompt: "Any year" }, { id: "select-year", include_blank: true, class: "form-select", multiple: true } %>
<br>
</div>
</div>
</div>
<%= f.submit "Search!", class: "button-orange" %>
<% end %>
So the specific city event index page is displaying properly following the scope but when I use the search I'm redirected to the main event index page with the search parameters that I inputted. I'm not understanding how the two different search form partials are both linked to the main event index page.
How do I get the new search form partial for the specific city index page to display the search results on its corresponding page?
The issue to my redirect form issue was that I need to set the URL of the new form I created, so I was able to get the proper path names from the routes.
My initial implementation though had my configuring the form specifically and since I wanted to reuse the form on multiple pages for the individual local cities, I was lucky to find an old 7 year old SOF article that referred to setting the url in the render line like this:
<%= render 'events/local_search_form', { url: atx_index_path } %>
and then calling that url string in the local_search_form like so:
<% if local_assigns.has_key? :url %>
<%= search_form_for #q, { url: url } do |f| %>
and now I can simply change the url path for each html.erb file I create for each individual city! I didn’t know about the local assigns!

NoMethodError in Discussions#create

Below is code extract from my file Users/JBossman/sites/discussions/app/views/discussions/_form.html.erb where line #20 raised this error undefined method 'map' for nil:NilClass
enter code here
<%= simple_form_for(#discussion) do |f| %>
<%= f.error_notification %>
<div class="field">
<div class="control">
<%= f.input :title, required: true, input_html: { class: 'input' }, wrapper: false, label_html: { class: "label" } %>
</div>
</div>
<div class="field">
<div class="control">
<%= f.input :content, required: true, input_html: { class: 'textarea' }, wrapper: false, label_html: { class: "label" } %>
</div>
</div>
<div class="field">
<label class="label">Channel</label>
<div class="control has-icons-left">
<span class="select">
<%= f.input_field :channel_id, collection:#channels.map { |c| [c.channel, c.id] }, prompt: "Select channel" %>
</span>
<span class="icon is-small is-left">
<i class="fa fa-tag"></i>
</span>
</div>
</div>
<div class="field">
<div class="control">
<%= f.button :submit, class:"button is-info" %>
</div>
</div>
<% end %>
the error is here: #channels.map { |c| [c.channel, c.id] }
your #channels variable is nil.
make sure you set #channels in your create action.
Looks like #channels is nil. Ideally, it should be initialized as [] empty array.
Alternatively, you can also use (#channels || []).map { |c| [c.channel, c.id] } to render empty form.
change #channels.map { |c| [c.channel, c.id] } to below, as #channels is not set(nil).
Assuming that you are having Channel model.
i. If you want to display all the channels in collection, then you can do it as below:
Channel.pluck(:channel, :id)
ii. If you want to display specific channels in collection, then define scope in Channel like favorites
Channel.favorites.pluck(:channel, :id)
I fixed a similar problem with a little change .map to .collect function, you may give it a try..

Rails: Show or Hide elements based on DropDown selection using IF

Using an IF Statement, How can I hide/show an element based on the value in the drop-down selection (transactionType)
Here's something I've been trying
Where did I go wrong?
<%= form_for(transaction) do |f| %>
<div class="field">
<%= f.label :transactionType %>
<%= f.select :transactionType, ['Income', 'Expense']%>
</div>
<%= if transaction.transactionType == 'Income' %>
<div class="field">
<%= f.label :amount %>
<%= f.number_field :amount, options = { max: 0 }%>
</div>
<% else %>
<div class="field">
<%= f.label :amount %>
<%= f.number_field :amount, options = { min: 0 }%>
</div>
<% end %>
How about using css classes..if its all about hiding elements from view...
So if you are using bootstrap,you can use hidden class.check more here.
in below example,i have used ternary operator which is just like if else.
for example:-
<%= form_for(transaction) do |f| %>
<div class="field">
<%= f.label :transactionType %>
<%= f.select :transactionType, ['Income', 'Expense']%>
</div>
<!-- here comes ternary operator -->
<div class="field <%= transaction.transactionType == 'Income' ? 'present' : 'hidden' %>">
<%= f.label :amount %>
<%= f.number_field :amount, options = { max: 0 }%>
</div>
<div class="field <%= transaction.transactionType != 'Income' ? 'present' : 'hidden' %>"">
<%= f.label :amount %>
<%= f.number_field :amount, options = { min: 0 }%>
</div>
if not using bootstrap,you can create custom class to hide/show elements.
Haven't used erb in quite some time, but I think you need to change
<%= if transaction.transactionType == 'Income' %>
should be
<% if transaction.transactionType == 'Income' %>
But this is only for the initial state. After that you will need jquery to toggle according to your select element

show rest of a form if a checkbox is ckecked in ruby on rails

I need to ask to my user if will pay a service with credit card...if it checked the option pay_with_card? it must show the rest of the form, that ask for other data like card number, mail, etc. if the user don't checked it, it must show a message, the question is...how can I do this? thanks in advance
<%= form_for(#product) do |f| %>
<%= f.label :pay_with_card? %>
<%= f.check_box :pay_with_card,{}, "Yes", "No"%>
<div>
<%= f.label :card_number %> <%= f.text_field :card_number %>
</div>
<div>
<%= f.label :mail %> <%= f.text_field :mail %>
</div>
<% end %>
Make the card number/mail details div style="display:none;", then add some javascript to the checkbox to change it to display:block;
Something like this:
<%= form_for(#product) do |f| %>
<%= f.label :pay_with_card? %>
<%= f.check_box :pay_with_card,{}, "Yes", "No"%>
<div id="card_details" style="display:none;">
<%= f.label :card_number %> <%= f.text_field :card_number %>
<%= f.label :mail %> <%= f.text_field :mail %>
</div>
<% end %>
<script type="text/javascript">
var checkbox = document.getElementById('product_pay_with_card');
var details_div = document.getElementById('card_details');
checkbox.onchange = function() {
if(this.checked) {
details_div.style['display'] = 'block';
} else {
details_div.style['display'] = 'none';
}
};
</script>
How about using jQuery?
First, wrap your credit card fields in a div with class credit_card_fields and than add this JS code to your page:
$("input[type='checkbox']#pay_with_card").on('change', function(){
$('.credit_card_fields').toggle();
});
You can use JS for it or move pay_with_card out of form like:
<%= link_to 'pay with card', your_current_path(:pay_with_card => 1) %>
<%= form_for(...) do |f| %>
<% if params[:pay_with_card] %>
<%= # fields for card %>
<% end %>
<% end %>
You can do it through jQuery, for example:
$ ->
$('select#pay_with_card').change ->
if $(this).val() == 'yes'
$('.card_block').slideDown('fast')
else
$('.card_block').slideUp('fast')
assumed that part of the form with payment card is included in the div with .card_block class
Ok my solution is this: all the code in the view, if a user check pay_with_card...(mi code is in spanish) it shows the complete form...if is not checked don´t show nothing, just the same checkbox asking for payment... thanks guys.
function mostrar (){
var checkbox = document.getElementById('chk_tarjeta');
if (checkbox.checked)
document.getElementById("card_details").style.display = "block";
else
document.getElementById("card_details").style.display = "none";
</script>
<h1>Forma de Pago</h1>
<%= form_for(#product) do |f| %>
<div id="product_pay_with_card">
<div >
<%= f.label :paga_con_tarjeta? %></br>
<%= f.check_box :paga_con_tarjeta, :id => "chk_tarjeta", :onclick => "mostrar();" %>
<div>
</div>
</div>
<div id="card_details" >
<div>
<%= f.label :numero_de_tarjeta %></br>
<%= f.text_field :numerotarjeta %>
</div>
<div>
<%= f.label :codigo_de_seguridad %></br>
<%= f.text_field :codigoseguridad %>
</div>
This worked for me with a form_with model and bootstrap
Change my_hidden_form with an id that makes sense for your form.
Original code is haml
= form_with scope: :model, url: models_path, local: true do |form|
.row
.col-6
.form-group
%h5.mb0 THE CASE TO TICK
= form.check_box :form_value, {:data => {:aria => {controls: :my_hidden_form, expanded: false}, :toggle => "collapse", :type => "checkbox", :target => "#my_hidden_form" }}
.row
.col-6
.form-group.collapse#my_hidden_form
%h5.mb0 THE FORM TO SHOW WHEN CASE IS TICKED
= form.text_field :name, placeholder: "A name"
.row
.col-md-12.text-right
= form.submit 'Submit', class: "btn btn-primary"
Converted to erb/html with https://haml2erb.org/
<%= form_with scope: :model, url: models_path, local: true do |form| %>
<div class="row">
<div class="col-6">
<div class="form-group">
<h5 class="mb0">THE CASE TO TICK
<%= form.check_box :form_value, {:data => {:aria => {controls: :my_hidden_form, expanded: false}, :toggle => "collapse", :type => "checkbox", :target => "#my_hidden_form" }} %>
</h5>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group collapse" id="my_hidden_form">
<h5 class="mb0">THE FORM TO SHOW WHEN CASE IS TICKED
<%= form.text_field :name, placeholder: "A name" %>
</h5>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 text-right">
<%= form.submit 'Submit', class: "btn btn-primary" %>
</div>
</div>
<% end %>
Since the approach suggested by #Unixmonkey didn't work for me, here's a slight variation I put together using an event listener.
<script type="text/javascript">
const checkbox = document.getElementById('product_pay_with_card');
const details_div = document.getElementById('card_details');
checkbox.addEventListener("change", (event) => {
if (event.currentTarget.checked) {
details_div.style['display'] = 'block';
}
else {
details_div.style['display'] = 'none';
}
});
</script>

Resources