HAML conditional tags - ruby-on-rails

I'm currently building a ruby on rails app using HAML as the template language. I'm looking to create a condition that defines a tag dependent on whether it is met or not, otherwise it defines a different tag. I know I could write this like:
- if ordered
%ol
- else
%ul
But this isn't particularly DRY, and would require me to duplicate the bulk of the code. Is there a very straightforward way to get around this problem? Should I be looking into ruby logic to find it?
Thanks

Define a helper. We'll introduce the ordered option to pick the tag, the rest are passed on to the tag.
# app/helpers/application_helper.rb
module ApplicationHelper
def list_tag(ordered: false, **opts)
kind = ordered ? :ol : :ul
haml_tag kind, **opts do
yield
end
end
end
And then,
-# some_view.html.haml
%p
Here's a list:
- list_tag ordered: false, class: 'some_class' do
- #list.each do |item|
%li
= item

If you need to do this logic in different views I think there are two approaches you can follow:
1. Make a partial and render it where you need this. If you need to pass variables use local_assigns
_my_list.html.haml
- if ordered
%ol
- else
%ul
use it
render 'partials/my_list', ordered: ordered
2. Make your own helper
def my_list(ordered)
if ordered
content_tag(:ol, class: 'my-class') do
# more logic here
# use concat if you need to use more html blocks
end else
content_tag(:ul, class: 'my-class') do
# more logic here
# use concat if you need to use more html blocks
end
end
end
use it
= my_list(ordered)
You can keep your ordered variable outside of the view and deal with the whole logic inside the helper.
If you ask yourself what to use, well the first answer from here is pretty good.

You can use the content_tag method as follows. content_tag documentation
= content_tag(ordered ? "ol" : "ul")
If you need it in several occasions you can put that in a helper method
module Listhelper
def list(ordered, html_options = {})
= content_tag(ordered ? "ol" : "ul")
end
end
Call it from the view with = list(your_variables)

Related

View helper method that lists images in a directory with `Dir.glob` and `each ... do` not working

My goal is to be able to include a helper method in my Rails 4 project that will list all images in a particular directory in a view so that I don't have to manually add image_tags each time I add a new image.
I've come across several examples of this, but for my purposes I'd like to allocate this job to a helper method, and I can't for the life of me understand why this isn't working.
myapp_helper.rb
module MyAppHelper
def list_logos(clss)
logos = Dir.glob("engines/myapp/app/assets/images/myapp/logos/*.{gif,png,jpg}")
logos.each do |logo|
content_tag("li", class: clss) do
image_tag logo.gsub("engines/myapp/app/assets/images/", "")
end
end
end
end
show.html.erb
<%= list_logos("companies__company") %>
This just prints out the Dir.glob array. Before, I had tried image_tag("myapp/logos/#{image.split('/').last}" to no avail, and so I thought I might have better luck with the gsub method. Nope.
Funny thing is, if, in my helper method, I just write:
logos = Dir.glob("engines/myapp/app/assets/images/myapp/logos/*.{gif,png,jpg}")
image_tag logos.sample.gsub("engines/petitions/app/assets/images/", "")
the image renders fine, which leads me to believe that it's the logos.each iteration which is failing.
I'm stumped. I'll add that this is an engines-based project that I've inherited, and I'm a relative newbie when it comes to Ruby and Rails, so I very well could be missing something simple. Yay! Thanks in advance.
You need to concatenate and return the tags. Try something like this:
module MyAppHelper
def list_logos(clss)
logos = Dir.glob("engines/myapp/app/assets/images/myapp/logos/*.{gif,png,jpg}")
logos.map do |logo|
content_tag("li", class: clss) do
image_tag logo.gsub("engines/myapp/app/assets/images/", "")
end
end.join
end
end
Also, since you're constructing HTML in the helper, you'll need to use html_safe in the template:
<%= list_logos("companies__company").html_safe %>
Oh, and the reason you saw the result of Dir.glob is that each returns the object it's called on.
module MyAppHelper
def list_logos(clss)
logos = Dir.glob("engines/myapp/app/assets/images/myapp/logos/*.{gif,png,jpg}")
list_items = logos.map do |logo|
content_tag("li", class: clss) do
image_tag logo.gsub("engines/myapp/app/assets/images/", "")
end
end
list_items.join
end
end

Rails 4.2 content_tag to output a table

I've been using content_tag with no issues to output div's or unordered lists etc. With that said, I can't figure out how to get content_tag to work with tables. I get quirky results. Ive seen similar questions mentioning that you have to use concat.
At a very basic level if I try something like this in a method:
def mymethod(item)
content = ""
content << content_tag(:table, :class => "table-striped") do
concat(content_tag(:thead)) do
headlabel = ["Header1","Header2"]
concat(content_tag(:tr)) do
headlabel.each do |hlabel|
concat(content_tag(:th, hlabel))
end
end
end
end # end content_tag table
return content.html_safe
end
This will be returned. The thead is empty
<table class="table-striped">
<thead>
</thead>
</table>
I'm not sure what I'm doing wrong.
Complex nesting with the content_tag helper is not ideal. When you nest multiple instances of the same HTML tag, only the last tag will be rendered, hence the need to concatenate. If you refactor like this, your thead will appear as expected:
content_tag(:thead) do
headlabel = ["Header1","Header2"]
content_tag(:tr) do
headlabel.each do |hlabel|
concat(content_tag(:th, hlabel))
end
end
end
In order to simplify your code and make it more readable, I would offer two options.
If the data in your table is simple, i.e it doesn't contain a bunch of conditional code, just use a standard erb partial and render it as needed. It's more readable and just as reusable as a helper method.
If your code is complex, with many conditionals and loops. Create a decorator class for your model. The quickest way is with the draper gem and it will allow you to write your presentation-logic OOP style and make testing much easier.

Rails HAML helper to display a list item

I am having difficulty getting my helper to display a list item. The markup looks like the following:
- #bars.each do |bar|
<% display_bar(bar) %>
The actual helper looks like the following:
module MyHelper
def display_bar(bar)
type = bar.type
concat(%li.type)
concat(%b some text)
concat(%i some more text)
end
end
What am I doing wrong here?
Such things has to be implemented via partials. Or see 5.
<% won't show you anyting. You're in Haml. It's ERb stuff (but even there it wouldn't have shown anything: you'd forgotten the = sign, it should have been <%=).
About concat(%li.type): you cant put your markup inside your Ruby code. Ruby knows nothing about your %li "code".
Amokrane Chentir already mentioned.
You're trying to reinvent the wheel. Rails already provides magnificent helper for such simple cases.
Take a look:
= content_tag_for(:li, #bars) do |bar|
%b= bar.title
%i= bar.id
UPD: content_tag_for sets styles/ids for each li tag based on the current model instance that makes it easy to implement styling/scripting in the future.
The name of your helper is display_bar not display_event.
You should use = instead of <% %>
- #bars.each do |bar|
= display_event(bar)
EDIT
Oops didn't read carefully the content of display_bar method, as #jdoe mentioned you can't use Haml markup syntax in your Ruby code.

Is there a way to disable default formtastic layout?

For one specific case I would like to render the form as a part of (for in-place editing). Is there a way in formtastic to disable the layout generated by .inputs / .buttons? Instead a
<fieldset> <ol> <li>
i would like simply to wrap the fields in the
<td>
Is there a build-in way or any solution to this problem?
There's no built in way (yet) in Formtastic to change the mark-up. Either use CSS to tweak the ample mark-up hooks in place, or ditch Formtastic for this form and code your own way (like we used to).
It's not yet supported, however you can use forked formtastic version:
https://github.com/linoj/formtastic
More details at:
http://www.vaporbase.com/postings/Replaceable_render_engines_for_Formtastic
Read on the formtastic forum that it might be even merge to origin someday.
In rails you can overrite the functions that define the tags that are used to render elements:
config/initializers/formtastic_foundation.rb:
# change required fields advice tag (abbr -> span)
Formtastic::FormBuilder.required_string =
proc { Formtastic::Util.html_safe(%{<span title="#{Formtastic::I18n.t(:required)}">*</span>}) }
module Formtastic
module Helpers
# change field wrapper (ol -> div)
module FieldsetWrapper
protected
def field_set_and_list_wrapping(*args, &block) #:nodoc:
contents = args.last.is_a?(::Hash) ? '' : args.pop.flatten
html_options = args.extract_options!
if block_given?
contents = if template.respond_to?(:is_haml?) && template.is_haml?
template.capture_haml(&block)
else
template.capture(&block)
end
end
contents = contents.join if contents.respond_to?(:join)
legend = field_set_legend(html_options)
fieldset = template.content_tag(:fieldset,
Formtastic::Util.html_safe(legend) << template.content_tag(:div, Formtastic::Util.html_safe(contents)),
html_options.except(:builder, :parent, :name)
)
fieldset
end
end
end
module Inputs
module Base
# change input wrapper tag (li.default_clases -> div.large-12.columns inside div.row)
module Wrapping
def input_wrapping(&block)
def super_wrapper_html_options
{:class => 'row'}
end
new_class = [wrapper_html_options[:class], "large-12 columns"].compact.join(" ")
template.content_tag(:div,
template.content_tag(:div,
[template.capture(&block), error_html, hint_html].join("\n").html_safe,
wrapper_html_options.merge(:class => new_class)),
super_wrapper_html_options)
end
end
end
end
end
I use this code to integrate Formtastic 3 with Foundation 5.4.5
I wrapped my call to the formtastic bit (in my haml file) in a string and then subbed out the
= "#{f.input ...}".gsub('<li class=', '<fart class=').html_safe #remove the li to align this input with the other text in the table.
It's a might bit easier than re-writing the form without formtastic, and it worked perfectly.
Admittedly it's a not an ideal solution. For a one off though... I can live with it.

Custom form helpers

Is there a way that I can create a custom form helper so that instead of:
special_field_tag :object, :method
I can achieve something like:
form.special_field :method
Yes, you can add to the FormBuilder class and get access to the object passed into the form_for. I've done this for a lot of things: dates, times, measurements, etc. Heres an example:
class ActionView::Helpers::FormBuilder
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
include ActionView::Helpers::CaptureHelper
include ActionView::Helpers::AssetTagHelper
# Accepts an int and displays a smiley based on >, <, or = 0
def smile_tag(method, options = {})
value = #object.nil? ? 0 : #object.send(method).to_i
options[:id] = field_id(method,options[:index])
smiley = ":-|"
if value > 0
smiley = ":-)"
elsif smiley < 0
smiley = ":-("
end
return text_field_tag(field_name(method,options[:index]),options) + smiley
end
def field_name(label,index=nil)
output = index ? "[#{index}]" : ''
return #object_name + output + "[#{label}]"
end
def field_id(label,index=nil)
output = index ? "_#{index}" : ''
return #object_name + output + "_#{label}"
end
end
Which you can use like this:
<% form_for #quiz do |f| %>
<%= f.smile_tag(:score) %>
<% end %>
There are some instance variables created by Rails that you can access in these helper methods:
#object - the model object specified by the form
#object_name - the class name of the object
#template - I think its an instance of the ActionView, you can possibly bypass all the includes I added by calling methods on the template. Haven't tried that yet.
#options - options passed to the FormBuilder when its created by the form_for call
I wrote the field_id and field_name methods to create these attributes on the HTML input elements the same way the regular helpers do, I'm sure there is a way to tie into the same methods that Rails uses, but I haven't found it yet.
The sky is the limit on what you can do with these helper methods, they simply return strings. You can create entire HTML tables or pages in one, but you better have a good reason to.
This file should be added in the app/helpers folder
#Tilendor, thanks so much for the pointers. Here is an example of an enum_select form tag helper that uses Rails 4.1 enums to automatically populate the options of a select tag:
# helpers/application_helper.rb
module ApplicationHelper
class ActionView::Helpers::FormBuilder
# http://stackoverflow.com/a/2625727/1935918
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
def enum_select(name, options = {})
# select_tag "company[time_zone]", options_for_select(Company.time_zones
# .map { |value| [value[0].titleize, value[0]] }, selected: company.time_zone)
select_tag #object_name + "[#{name}]", options_for_select(#object.class.send(name.to_s.pluralize)
.map { |value| [value[0].titleize, value[0]] }, selected: #object.send(name))
end
end
end
The trickiest construct is #object.class.send(name.to_s.pluralize) which produces a hash of available values (e.g., Company.time_zones). Putting it in helpers/application_helper.rb makes it automatically available. It is used like:
<%= f.label :billing_status %>:
<%= f.enum_select :billing_status %><br />
Our app was displaying phone numbers in text fields, and we wanted to omit the country code for domestic numbers. I was using form helpers. After reading this and rails source a bit, i came to this solution:
class ActionView::Helpers::FormBuilder
def phone_text_field name, options = {}
value = object.public_send(name).to_s
if value.start_with? "9" # national number
value = value[1..-1]
options["value"] = value
end
text_field name, options
end
end
I put this on app/helpers/application_helper.rb and use it like i use text_field() helper. Hope this helps someone.

Resources