simple_form collection_Radio_buttons generates extra labels - ruby-on-rails

I have the following Ruby:
<%= simple_form_for #answer, url: presentation_survey_path(#presentation) do |f| %>
<%= f.collection_radio_buttons :option_id, #question.options_array, :first, :last, {label: false} do |b| %>
<div class="col-sm-4">
<%= b.label(class: 'btn btn-large', style: 'margin-right: 20px; margin-left: 20px;') {b.radio_button(id: b.object.last, class: 'answer-input', style: 'display: none;') + b.text } %>
</div>
<% end %>
And it is correctly generating html except that it is generating two labels:
<span>
<label for="answer_option_id_15">
<div class="col-sm-4">
<label class="btn btn-large" for="answer_option_id_15" style="margin-right: 20px; margin-left: 20px;">
<input class="answer-input" id="Way too many" name="answer[option_id]" style="display: none;" type="radio" value="15" />
Way too many
</label>
</div>
</label>
</span>
It is gennerating the first label for some reason. I only want to keep the second one. Label: false is not working. How do I get rid of the first label?

According to the docs:
For check boxes and radio buttons you can remove the label changing
boolean_style from default value :nested to :inline.
In addition, simple_form has some options for collection_radio_buttons that are documented in the code:
# Collection radio accepts some extra options:
#
# * checked => the value that should be checked initially.
#
# * disabled => the value or values that should be disabled. Accepts a single
# item or an array of items.
#
# * collection_wrapper_tag => the tag to wrap the entire collection.
#
# * collection_wrapper_class => the CSS class to use for collection_wrapper_tag
#
# * item_wrapper_tag => the tag to wrap each item in the collection.
#
# * item_wrapper_class => the CSS class to use for item_wrapper_tag
#
# * a block => to generate the label + radio or any other component.
In my case setting item_wrapper_tag to false and boolean_style to :inline did the trick, e.g.:
<%= f.collection_radio_buttons :option_id, #question.options_array, :first, :last, boolean_style: :inline, item_wrapper_tag: false do |b| %>

Related

How can I DRY this <div> with Rails Forms?

I understand that with Rails Form Helpers, I can pass in classes, placeholders, types, etc. into Rails, so instead of writing
<input id="name" name="name" type: "text" pattern="" value="Kanye West"/>
<label class="mdl-textfield__label" for="name">Enter Name</label>
I can do:
<%= f.text_field :name, class: 'mdl-textfield__input', type: 'text', pattern: '-?[0-9]*(\.[0-9]+)?', id: 'name', value: 'Kanye West' %>
<label class="mdl-textfield__label" for="name">Enter Name</label>
But it becomes pointless, because if I use a CSS framework, it ends up looking like this...
<div class="mdl-cell mdl-cell--6-col mdl-cell--4-col-tablet hide-dis">
<div class="mdl-textfield advanced-input mdl-js-textfield mdl-textfield--expandable">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<%= f.text_field :name, class: 'mdl-textfield__input', type: 'text', pattern: '-?[0-9]*(\.[0-9]+)?', id: 'name', value: 'Kanye West' %>
<label class="mdl-textfield__label" for="name">Enter Name</label>
</div>
</div>
</div>
You can imagine how ugly this can get when you have 10 form elements, right. I could try partializing it, but I don't know how I would pass in the f.textfield part. Please let me know how I can improve this mess and or anything else that you see that can be improved with me code :)
You can create your own FormBuilder and create custom controls that include the wrapper classes than you need.
For example if you want to wrap your text fields in a particular div:
class CustomFormBuilder < ActionView::Helpers::FormBuilder
def custom_text_field(method, tag_value, options = {})
#template.content_tag(:div,
#template.text_field(
#object_name, method, tag_value, objectify_options(options)
),
class: 'mdl-textfield mdl-js-textfield'
)
end
end

Rails 5 Simple Form Save Hash Value Submitted by a Form in Model Attribute

In my Rails 5 app I have a Simple Form form for creating or updating for my model Training. In this form I already have checkboxes for each of my Participants to assign them to the Training. Now I'm trying to add a radio button group (5 for a rating from 1 to 5) for each Participant, because each can rate the Training.
Screen shot of the rendered form
I managed to render the radio buttons as I wanted but unfortunately I can only select one of all the radio buttons rendered. They are even strangely connected to the checkboxes, which will probably be because they have the same input method participant_ids, which is connected to the has_and_belongs_to_many association with Participant :participants.
<%= simple_form_for #training do |f| %>
...
<div id="participants-ratings-container">
<div id="participants-container" style="display: inline-block; vertical-align: top; width: 8em">
<%= f.label t('activerecord.models.participant') %>
<%= f.collection_check_boxes :participant_ids, Participant.all.order(:name), :id, :name, {:item_wrapper_class => 'checkbox-container'} %>
</div>
<div id="ratings-container" style="display: inline-block; vertical-align: top">
<%= f.label t('trainings._form.ratings') %>
<% Participant.all.order(:name).each do |p| %>
<%= f.input :participant_ids, collection: 1..5, as: :radio_buttons, label: false%>
<% end %>
</div>
...
<% end %>
I tried a lot of things but I have no ideas anymore. If it is possible I would like to fill a Hash participant_ratings in my Training class in the way "participant.name" => rating value.
Can anyone give me a hint on how to achieve this? Maybe even tell me how to disable the radio button group if the corresponding Participant is not checked?
Thanks in advance!
UPDATE
I managed to adapt my form and even integrate jQuery raty plugin to enter the rating value for each Participant. Each rating is sent with it's corresponding participant-id to the server as a hash, e.g. like so:
"participant_ratings"=>{"3"=>"5", "1"=>"3.5", "2"=>""}.
Every rating is disabled and it's value set to 0 if the corresponding participant checkbox isn't checked. Here is my view code:
<div id="ratings-container" style="display: inline-block; vertical-align: top">
<%= f.label t('trainings._form.ratings') %>
<% Participant.all.order(:name).each do |p| %>
<span class="checkbox_container">
<% current_rating = #training.participant_ratings[p.id].nil? ? '' : #training.participant_ratings[p.id] %>
<label id='star<%= "#{p.id}" %>' data-rating='<%= "#{current_rating}" %>' style="margin-bottom: 12px;" ></label>
<input id="rating<%= "#{p.id}" %>" name="participant_ratings[<%= "#{p.id}" %>]" type="hidden" value="<%= "#{#training.participant_ratings[p.id]}" %>" />
</span>
<script>
$('#star<%= "#{p.id}" %>').raty({
score: function() {
return $(this).attr('data-rating');
},
half : true,
readOnly: <%= !#training.participants.include?(p) %>,
path: '/assets',
click : function(score, evt) {
$('#rating<%= "#{p.id}" %>').val(score);
}
});
$('#training_participant_ids_<%= "#{p.id}" %>').change(function () {
var star = $('#star<%= "#{p.id}" %>');
if(document.getElementById('training_participant_ids_<%= "#{p.id}" %>').checked) {
star.raty('readOnly', false);
} else {
star.raty('cancel');
star.raty('readOnly', true);
$('#rating<%= "#{p.id}" %>').val("");
}
})
</script>
<% end %>
</div>
New screen shot of the rendered form
The last thing I don't seem to be able to figure out is how to save the participant_ratings hash in the corresponding model attribute :participant_ratings
If I allowed the param :participant_ratings in my trainings_controller.rb but nothing was saved. So I added
#training.participant_ratings = params[:participant_ratings]
and now I get the error
Attribute was supposed to be a Hash, but was a ActionController::Parameters. -- <ActionController::Parameters {"3"=>"5", "1"=>"3.5", "2"=>""} permitted: false>
The methods in my trainings_controller.rb:
class TrainingsController < BaseController
...
def update
#training = Training.find(params[:id])
#training.updated_date_time = DateTime.now
#training.participant_ratings = params[:participant_ratings]
if #training.update(training_params)
flash[:notice] = t('flash.training.updated')
redirect_to #training
else
render 'edit'
end
end
private
def training_params
params.require(:training).permit(:village, :topic, :user_id, :start_time, :end_time, {:participant_ids => []}, :participant_ratings)
end
end
How do I save the hash sent by the form in the model's attribute? Where am I going wrong?
Can I somehow define participant_ratings as a hash in the params similar to the array participant_ids there?
Firstly, I had to make sure that the participants_rating-hash was inside the training-hash when submitted by my form, so I had to change the name attribute in my hidden form input for the rating:
<input id="rating<%= "#{p.id}" %>" name="training[participant_ratings][<%= "#{p.id}" %>]" type="hidden" value="<%= "#{#training.participant_ratings[p.id]}" %>" />
Secondly, I had to whitelist the keys for my participant_ratings-hash so that they are permitted:
def training_params
ratings_keys = params[:training].try(:fetch, :participant_ratings, {}).keys
params.require(:training).permit(:village, :topic, :user_id, :start_time, :end_time, {:participant_ids => []}, participant_ratings: ratings_keys )
end
I found this solution in this rails thread
After that participant_ratings was saved just fine!

One form element not being rendered when validation fails

I have not been able to figure out if this is a simple_fields_for problem, a cocoon issue, or something else. If I have blundered with a </div> placement, I don't see it.
When the form first displays, it renders an input field for protocol name. The user can click buttons to add form elements of interest. This works fine and looks like this:
Here's how it looks after the user has clicked on each of the "Add an element" buttons:
The user can add 0..many of each of the elements. When they click on 'Save' it all works really well.
If there is a validation error in one of the fields, the form re-renders fine with one exception. Validation errors for the "Imaging Step" elements do not re-display at all. The other elements re-render and are highlighted as expected when validation fails.
Here's a pictorial example. The user fills out part of the form, having forgotten to select a "Sequence" and having forgotten to enter text for "Tip Description":
After clicking 'save' and failing validation, the form re-renders like this:
As you can see, the Imaging Step section has not been redrawn.
If I look at params in the context of the view, everything seems to be there. #protocol.errors looks right to me as well. Models seem OK too.
Here is a pastebin of the form code.
Here is a pastebin of _step_item_fields.html.erb.
Here is a pastebin of _tip_fields.html.erb.
Here is a pastebin of my Gemfile.
UPDATE:
if I build a step_item like this:
<div id="step_items">
<%= f.simple_fields_for :step_items, #protocol.step_items.build do |si| %>
<%= render 'step_item_fields', :f => si %>
<%end%>
</div>
The Imaging Step section is always drawn, but (obviously) does not populate when validation fails. This also confuses the feature of letting the user add/remove 0..many Imaging Steps.
I also tried:
<div id="step_items">
<%= f.simple_fields_for :step_items, #protocol.step_items.build(protocol_params) do |si| %>
<%= render 'step_item_fields', :f => si %>
<%end%>
</div>
... and other variants when protocol_params is available, but ran in to forbidden params issues.
UPDATE2:
I also tried building a hash with params for a step_item. I can use this here:
<div id="step_items">
<%= f.simple_fields_for :step_items, #protocol.step_items.build(some_ok_params) do |si| %>
<%= render 'step_item_fields', :f => si %>
<%end%>
</div>
... but only for a single step_item. Am not sure how to pass a hash of hashes reflecting the 0..many functionality. Also, building a step_item this way populates the form element correctly, but does not include the error highlighting styling. This is about the time I started wondering why the simpler solution was not working.
Since StepItem is a sub-type of the STI class Step, I think the error detection for StepItems was getting missed. My hack fix here is not pretty and admittedly brittle. Please post a better way if you have one.
I changed this in _form.html.erb:
...
<%if params["protocol"] && params["protocol"]["step_items_attributes"].present?%>
<%params["protocol"]["step_items_attributes"].each do |sia|%>
<%v = sia[1]%>
<div id="step_items">
<%= f.simple_fields_for :step_items, #protocol.step_items.build(:orientation_id => v["orientation_id"], :sequence_id => v["sequence_id"], :note => v["note"], :id => v["id"], :protocol_id => v["protocol_id"], :institution_id => v["institution_id"] ) do |si| %>
<%= render 'step_item_fields', :f => si%>
<% end %>
</div><!-- end step_items-->
<%end%>
<%else%>
...
I changed this in _step_item_fields.html.erb:
...
<div class="nested-fields">
<div class= "form-inputs">
<div class="row">
<div class="col-sm-2 text-right">Imaging Step:</div>
<div class="col-sm-2 drop_col ">
<%= f.collection_select :orientation_id, Orientation.where("institution_id=?",current_user.current_institution_id),:id,:name, {:prompt => "Pick an Orientation", }, {class: "form-control #{"dropdown_error" if f.object.orientation_id.blank? && (f.object.sequence_id.present? || f.object.note.present?)}"}%>
</div>
<div class="col-sm-2 drop_col ">
<%= f.collection_select :sequence_id, Sequence.where("institution_id=?",current_user.current_institution_id),:id,:name, {:prompt => "Pick a Sequence"}, {class: "form-control #{ "dropdown_error" if f.object.sequence_id.blank? && (f.object.orientation_id.present? || f.object.note.present?) }"}%>
</div>
</div>
<div class="row">
<div class="col-sm-2"></div>
<%= f.input :note, :wrapper_html =>{:class => 'col-sm-8'}, label: false, placeholder: 'You can put an optional note here.' , :input_html => {:size => 50}%>
<%= f.input :institution_id,:as => :hidden, :input_html => {:value=>current_user.current_institution_id} %>
<%= f.input :id,:as => :hidden %>
<div class="col-sm-2">
<%= link_to_remove_association "Remove Imaging Step", f , :class=>"btn btn-danger btn-xs placemid",title: "Click here to delete this imaging step.", data: {toggle: "tooltip", placement: "auto", animation: true, delay: {show: 700, hide: 100}}%>
</div>
</div>
</div>
<div class="col-sm-12"><hr></div>
</div>
...
And I added this to the correct stylesheet:
.dropdown_error{
color: red;
}
The form now displays StepItem errors correctly as seen here:

Rails is defaulting to capitalizing only first letter of first word, rather than what I type into string

Just curious if there was a rule about this.... and how to get around it. Basically when I call a variable with a string that is Daypack (<40L) or `Tent (1-Person)" for example, whatever the content, the output in the view only capitalizes the first letter of the first word. And what I want is for whatever I type in caps to be capitalized in the view.
With the following hash in the controller:
#itemlist = {
"Camping" => ["Tent (1-person)", "Tent (2-person)", "Tent (3-person)", "Tent (4-person)", "Tent (6-person)", "Tent (8-person)", "Sleeping bag", "Sleeping pad", "Camp pillow", "Daypack (<40L)", "Daypack cover"]
}
In the view I'm teasing part the category (camping) and the items within that category (the array associated with Camping) are assigned to label_tags. That's where the capitalization is messing up.
<% #itemlist.each do |category, list| %>
<div class="col-xs-4">
<div class="form-group" style="font-size:80%">
<h5> <%="#{category}"%> </h5>
<% list.each do |thing| %>
<%= label_tag "#{thing}" %>
</br>
<% end %>
</div>
</div>
<% end %>
label_tag takes one, two or three arguments. If you give it one, it's the name of the label, and it guesses the tag's text. If you want to control the tag's text, give it two arguments:
<%= label_tag thing, thing %>
From the documentation:
label_tag 'name'
# => <label for="name">Name</label>
label_tag 'name', 'Your name'
# => <label for="name">Your name</label>
label_tag 'name', nil, class: 'small_label'
# => <label for="name" class="small_label">Name</label>

Defining field label in a block - on error, field_with_errors div tag not in right place

I am using validates_acceptance_of :terms, :message => "must be accepted" in my user.rb model, and am using bootstrap-sass.
My check box code looks like this in the view:
<div class="control-group">
<%= f.label :terms, :class => "control-label" do %>
Accept <%= link_to('Terms of Use *', "#myTOUModal", :"data-toggle" => "modal") %>
<% end %>
<div class="controls">
<%= f.check_box :terms %>
</div>
</div>
For some reason, when the terms check box isn't selected on form submission, the appropriate error message shows up at the top of the form, but there is a problem with the field_with_errors div class wrapping around the check box label.
The HTML for the rendered page looks like this:
<div class="control-group">
<label class="control-label" for="user_terms">
Accept Terms of Use *
</label>
<div class="controls">
<input name="user[terms]" type="hidden" value="0" />
<div class="field_with_errors">
<input id="user_terms" name="user[terms]" type="checkbox" value="1" />
</div>
</div>
</div>
The result is that the check box field label isn't highlighted on error. Is there a way to force the div tag placement for the field_with_errors class to show up just after the <div class="control-group"> tag? Why does using a block to define a field label throw off the field_with_errors tag placement? Does anyone have experience with this?
Thank you
This is a bug i think. The problem is in block. Define your label without block and everything works.
Try something like:
<% modal_html = capture do >
Accept <%= link_to('Terms of Use *', "#myTOUModal", :"data-toggle" => "modal") %>
<% end %>
<%= f.label :terms, modal_html, :class => "control-label" %>
Or helper:
def modal_html
#Q{Accept #{link_to('Terms of Use *', "#myTOUModal", :"data-toggle" => "modal")} }.html_safe
end

Resources