Conditional classNames & collection_check_boxes - ruby-on-rails

In this example I am displaying all possible Tags on a Post when creating or editing a Post. I want to change the background-color to indicate if an item is checked or not, so I can remove the checkbox. You can see my failed attempt where I conditionally try to change the background-color in the class attribute of tag_item.label.
How can I check if tag_item is checked?
<div class="flex flex-wrap gap-3">
<%= form.collection_check_boxes :tag_ids, Tag.all, :id, :name do |tag_item| %>
<%= tag_item.label class: "#{tag_item.check_box ? "bg-red-500" : "bg-gray-100"} select-none border2 border-gray-100 flex w-auto p-2 text-sm cursor-pointer text-center" %>
<%= tag_item.check_box do |tag_item_checkbox| %>
<p> <%= tag_item_checkbox.inspect %> </p>
<% end %>
<% end %>
</div>

So, where you're doing the tag_item.check_box ? "bg-red-500" : "bg-gray-100", that tag_item.check_box needs to really be checking whether the tag_item of this iteration is set on the post model.
You might already have a way of referring to the given post (an #post or post local), or you can refer to it in a form context with form.object, so then you need to check whether the current tag_item is in the post's tags, eg:
<%= tag_item.label class: "#{ post.tags.include?(tag_item) ? "bg-red-500" : "bg-gray-100" } other classes" ...etc
Make sense?

So Im a new Rails developer but solved the problem my way, forgive me that I could not find the "rails way" to this problem. But anyways - it works like a charm.
tag_item.check_box.inspect["checked"] is returning if the tag is applied to the post (in edit view). The toggle(tag_item_id) function is therefore only for setting the background-color client-side as clollection_check_boxes and tag_item.check_box are handling the checked-status and the data when the form get submitted.
...
<div class="flex flex-wrap gap-3">
<%= form.collection_check_boxes :tag_ids, Tag.all, :id, :name do |tag_item| %>
<div id="post_tag_id_<%= tag_item.object.id %>">
<%= tag_item.label class: "#{tag_item.check_box.inspect["checked"] ? "bg-red-500" : "bg-gray-100"} select-none border2 border-gray-100 flex w-auto p-2 text-sm cursor-pointer text-center" %>
<%= tag_item.check_box class: "hidden", onclick: "toggle(#{tag_item.object.id})" %>
</div>
<% end %>
</div>
...
<script>
function toggle(tag_item_id) {
const label = document.querySelector("#post_tag_id_" + tag_item_id + " > label");
const checkbox = document.getElementById("post_tag_ids_" + tag_item_id);
if (!checkbox.checked) {
label.classList.remove("bg-red-500");
label.classList.add("bg-gray-100");
} else {
label.classList.remove("bg-gray-100");
label.classList.add("bg-red-500");
}
}
</script>

Related

editing multiple child records while accessing values of other attributes of said record

For class
class Product
has_many :productunits
accepts_nested_attributes_for :productunits
class Productunit
belongs_to :product
belongs_to :unit
validates :product_id, presence: true
validates :unit_id, presence: true
the following form is meant only to update existing records (of what is effectively a join table), while formatting the fields in columns (one column per child) and effecting some view logic on whether the field should be shown or not.
<div class='grid-x grid-margin-x'>
<%= f.fields_for :productunits do |price_fields| %>
<div class='cell small-2 text-right'>
<h4><%# productunit.unit.name %> </h4>
<%# if productunit.unit.capacity == 2 %
2 <%= label %> <%= price_fields.number_field :price2 %>
<%# end %>
</div>
<% end %>
</div>
However a number problems are arising:
I cannot invoke the value of an attribute of record being edited (say productunit.unit.capacity)
The natural break in child records is not accessible to html tags for formatting (<div class='cell [...]). Worse, rails is throwing the child record id outside the div definition </div>
<input type="hidden" value="3" name="product[productunits_attributes][1][id]" id="product_productunits_attributes_1_id" />
<div class='cell small-2 text-right'>
submitting the form returns an error Productunits unit can't be blankwhich would be fair for a new record, but is definitely not expected when editing an existing one.
Unfortunately, the rails guide is thin in this regard.
I cannot invoke the value of an attribute of record being edited
You can get the object wrapped by a form builder or input builder though the object method:
<div class='grid-x grid-margin-x'>
<%= f.fields_for :productunits do |price_fields| %>
<div class='cell small-2 text-right'>
<h4><%= price_fields.object.unit.name %> </h4>
<% if price_fields.object.unit.capacity == 2 %
2 <%= price_fields.label :price2 %> <%= price_fields.number_field :price2 %>
<% end %>
</div>
<% end %>
</div>
2 The natural break in child records is not accessible to html tags
for formatting...
fields_for just iterates through the child records. Its just a loop. I'm guessing you just have broken html like a stray </div> tag or whatever you're doing with <%= label %>.
submitting the form returns an error Productunits unit can't be blankwhich would be fair for a new record, but is definitely not expected when editing an existing one.
You're not passing a id for the unit. Rails does not do this automatically. Either use a hidden input or the collection helpers.
<div class='grid-x grid-margin-x'>
<%= f.fields_for :productunits do |price_fields| %>
<div class='cell small-2 text-right'>
<h4><%= price_fields.object.unit.name %> </h4>
<% if price_fields.object.unit.capacity == 2 %
2 <%= price_fields.label :price2 %> <%= price_fields.number_field :price2 %>
<% end %>
<%= price_fields.collection_select(:unit_id, Unit.all, :id, :name) %>
# or
<%= price_fields.hidden_field(:unit_id) %>
</div>
<% end %>
</div>
On a side note you should name your model ProductUnit, your table product_units and use product_unit everywhere else. See the Ruby Style Guide.

Can I build several objects in different parts of a page?

I have a Page model that has many TextBlock:
class Page < ApplicationRecord
has_many :text_blocks, as: :textable, dependent: :destroy
accepts_nested_attributes_for :text_blocks
end
In the form where a page can be edited, I have to display 3 text blocks in the upper part of the form, and 3 text blocks in the lower part of the form. Here is the code in my view...
Upper text blocks:
<%= render 'shared/admin/form-fields/text-blocks-form', f: f, number: 3, value: true %>
Lower text blocks:
<%= render 'shared/admin/form-fields/text-blocks-form', f: f, number: 2, value: false %>
And here is the text-blocks-form partial:
<div class="form-group">
<div class="row">
<div class="col-lg-12 text-blocks">
<div class="row">
<% number.times { f.object.text_blocks.where(upper_position: value).build } unless f.object.text_blocks.where(upper_position: value).any? %>
<%= f.fields_for :text_blocks do |input| %>
<div class="<%= "col-lg-#{cells(number)}" %>">
<%= render 'shared/admin/form-fields/text-blocks-fields', f: input, value: value %>
</div>
<% end %>
</div>
</div>
</div>
</div>
Upper text blocks are built as expected, there are 3 blocks but in the lower part of the form, there are 5 blocks instead of 2. It seems, that it just add 3 + 2. Is there any way to build text blocks as it was described above? Thanks ahead.
I can easily explain why you have this problem, maybe a little less easy to fix this in good way.
The reason why have this that in the first run you create three nested items (on the association), and then iterate over them using f.fields_for :text_blocks, and in the second run you add two more, and then iterating over f.fields_for :text_blocks will iterate over all five created blocks.
An easy fix would be to change your view as follows:
<div class="form-group">
<div class="row">
<div class="col-lg-12 text-blocks">
<div class="row">
<% number.times { f.object.text_blocks.where(upper_position: value).build } unless f.object.text_blocks.where(upper_position: value).any? %>
<%= f.fields_for :text_blocks do |input| %>
<%- if input.object.upper_position == value %>
<div class="<%= "col-lg-#{cells(number)}" %>">
<%= render 'shared/admin/form-fields/text-blocks-fields', f: input, value: value %>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
</div>
If you always have 5 blocks (3 on top and 2 below), I would also consider adding a position or order column, allowing the textblocks to be re-rendered in the correct position after save as well.

Use of check_box_tag, how get his value?

I´m working to generate a PDF file with an option, this option makes visible a logo in the final PDF, I´m new in this and can´t hit the right answer
I made a plain check_box and then tried to catch a parameter, but this don´t work: <%= check_box_tag :logoless, "true", true %>
<a href="<%= pcf_generator_path(#legal_vacation, logoless: :logoless )%>" data-toggle="tooltip" data-placement="bottom"
title="Comprobante de Feriado"class="btn btn-primary" role="button" aria-pressed="true" target= '_blank'>
<%= "PCF" %></a>```
I want to be able to catch the value of a checkbox in url or in #legal_vacation variable (or directly if it´s possible).
http://localhost:3000/legal_vacations/pcf_generator/2?logoless=true <= like this
Finally I resolved using an attr_accessor called logoless.
Then I create a form with a check_box:
<%= form_for(#legal_vacation, url: request_document_path(id: #legal_vacation.id),
html: { method: "get" }) do |f|%>
<div class="form-row">
<div class="col-8 text-center">
<%= f.submit 'PCF', class: 'btn btn-primary'%>
</div>
<div class="col text-center">
<%= f.check_box :logoless %>
</div>
</div>
<% end %>
And in my controller I take that value to assign it in my pdf's:
def request_document
#legal_vacation= LegalVacation.find(params[:id])
vacation = LegalVacation.new(legal_vacation_params)
#logo = vacation.logoless
if params[:commit] == 'PCF'
document_type = "pcf"
end
pdf_info(document_type)
end
And that all!

Can one use conditions and loops on a single line in Ruby?

How would one go about turning the following code into the latter?
<div id="faqs">
<% if #faqs.length > 0 %>
<% #faqs.each do |faq| %>
<div class="faq">
<strong>Q:</strong> <%= faq.question %>
<br />
<strong>A:</strong> <%= faq.answer %>
</div>
<% end %>
<% else %>
<p>No FAQs to display.</p>
<% end %>
</div>
<div id="faqs">
<% #faqs.empty? ? content_tag(:p, "No FAQs to display.") : #faqs.each do |faq| %>
<div class="faq">
<strong>Q:</strong> <%= faq.question %>
<br />
<strong>A:</strong> <%= faq.answer %>
</div>
<% end %>
</div>
I'm curious as to whether I can get the latter code to work. The only element of it that is failing at the moment is that the content_tag() is not displaying - this is due to the fact that I'm not using printable ruby tags (<%= # %>) but using them will dump out the FAQ objects underneath the content.
I considered the use of puts() to print the content_tag() while inside the ruby tags but that didn't work.
I've tried to search for this issue but haven't yielded anything useful.
Is this achievable and if so, does it have any benefits other than being prettier?
One way to make the later code to work if you can put the body of the loop in a helper function and return the out put of content_tag from that. The line in view file might be somewhat like this.
<%= #faqs.empty? ? content_tag(:p, "No FAQs to display.") : printList(#faqs) %>
and your printList function will return the output of nested content_tags. You can make a generic list printing function which can be used for any list.
Something so obvious but still shared.
This should work (for clarity, I moved FAQ tag generation in separate helper method):
<div id="faqs">
<%= raw (#faqs.empty? ? content_tag(:p, "No FAQs to display.") : #faqs.map { |faq| faq_div(faq) }.join) %>
</div>
or, perhaps more clean:
<div id="faqs">
<%= content_tag(:p, "No FAQs to display.") if #faqs.empty? %>
<%= raw #faqs.map { |faq| faq_div(faq) }.join %>
</div>
meanwhile, in helpers:
def faq_div(faq)
'<div class="faq"><strong>Q:</strong> %s<br /><strong>A:</strong> %s</div>' % [faq.question, faq.answer]
end
This should work:
<% if #faqs.each do |faq| %>
<div class="faq">
<strong>Q:</strong> <%= faq.question %>
<br />
<strong>A:</strong> <%= faq.answer %>
</div>
<% end.empty? %>
<p>No FAQs to display.</p>
<% end %>

Using Typeahead from Twitter Bootstrap in a form (formtastic)

How do I integrate a typeahead from bootstrap like this:
<input type="text" class="span3" style="margin: 0 auto;" data-provide="typeahead" data-items="8" data-source='["University of Pennsylvania","Harvard","Yale","Princeton","Cornell","Brown","Columbia","Dartmouth"]'>
into a standard form like this:
<%= semantic_form_for(#education) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.input :college, placeholder: "Update Education" %>
</div>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
In your controller
def index
#autocomplete_items = Model.all
end
In your view, just like you have with an additional ID for the selector...
<% semantic_form_for(#education) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.input :college, placeholder: "Update Education", id: "auto_complete" %>
</div>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
And most importantly, pass the #autocomplete_items instance variable defined in your controller into a Javascript variable in your view:
<%= javascript_tag "var autocomplete_items = #{ #autocomplete_items.to_json };" %>
This will serialize your data and make it usable JSON for the Typeahead function to use.
As for the Typeahead, simply pass that object (#autocomplete_items) as JSON to the Javascript like so:
<script type="text/javascript">
jQuery(document).ready(function() {
$('#auto_complete').typeahead({source: autocomplete_items});
});
</script>
Additionally there is an Autocomplete gem for Rails 3 which will work directly with your models rather than passing off the object to your Javascript. There is even a Formtastic example in the documentation.
Edit: It looks like I didn't read your whole question! Unfortunately HTML5 Data Attributes are currently unsupported with Formtastic. There is however a separate branch that does include support for these attributes.
Other than that there's always just sticking with Good ol' HTML/ERB for dynamic features like this...
<input type="text" class="span3" style="margin: 0 auto;" data-provide="typeahead" data-items="8" data-source='<%= #autocomplete_items.to_json %>'>
EDIT 2: I've just noticed two things. First being the way I was passing the JSON object to a Javascript variable (see the example). Secondly, the above example using HTML5 data attributes will not work with Twitter's Typeahead plugin, but it will work with the jQuery UI Autocomplete plugin.
I got it worked like:
Controller
#categories = Category.find(:all,:select=>'name').map(&:name)
and views
<input type="text" class="span3" style="margin: 0 auto;" data-provide="typeahead" data-items="8" data-source='<%= #categories.to_json %>'>

Resources