Best way to handle data attributes in Slim - ruby-on-rails

I was evaluating Slim as a replacement for HAML in a personal project, and it doesn't appear to handle HTML5 data attributes as gracefully as HAML. I was hoping someone may have also run into this, or may have known about an option/syntax I haven't yet found in their docs.
HAML allows you to define HTML 5 data attributes simply by using nested hashes like so:
%a{data: {key1: 'val', key2: 'val'}}
resulting in
<a data-key1='val' data-key2='val'></a>

There are multiple ways in Slim
As Hash
Attributes which will be hyphenated if a Hash is given (e.g. data={a:1,b:2} will render as data-a="1" data-b="2")
Use it directly as "mu is too short" mentioned, quite intuitive.
a data-title="help" data-content="foo"
Use Ruby code. I often do this and rarely above.
= link_to 'foo', bar_path, data: {a: 'a', b: 'b'}

Use the splat operator:
h1#section-title*{'data-url'=>'test', 'data-id'=>'test'} = #project.name

.your-class*{data: {first_attribute: 'first value', second_attribute: 'second value'} }
Will produce
<div class="your-class" data-first_attribute="first value" data-second_attribute="second value"></div>

I prefer this kind to fix...
#products.each do |product|
.module data-id=product.id
It is working for me

Related

Conditionally creating a hidden attribute in Slim with Ruby on Rails

I'm trying to get Slim to generate the following HTML if a CONDITION is true:
<div id="start_button" hidden="hidden">
I have tried various methods, like the obvious:
#start_button #{('hidden="hidden"' if CONDITION?)}
= link_to 'Get Started', ...etc...
but that generates:
<div id="start_button">hidden="hidden"
= link_to 'Get Started',..etc...
I know how to do it when setting an attribute like class etc. to something, but because it has got to be all or nothing with "hidden", it's causing me issues.
I've been down quite a few rabbit holes this evening, so any help appreciated!
This is stated in the docs:
Ruby attributes
Write the ruby code directly after the =. If the code contains spaces
you have to wrap the code into parentheses (...). You can also
directly write hashes {...} and arrays [...].
So, anything that's inside () is evaluated as Ruby code, if the statement is evaluated to false, it's somehow "skipped", otherwise it does what's stated in the true branch.
#start_button hidden=('hidden' if true)
<div hidden="hidden" id="start_button"></div>
#start_button hidden=('hidden' if false)
<div id="start_button"></div>

Dissecting an ERB to HAML conversion example

Directly rom the HAML tutorial:
<div class='item' id='item<%= item.id %>'>
<%= item.body %>
</div>
and the answer is
.item{:id => "item#{item.id}"}= item.body
I understood the reason for first .item, Ok it is a class so we declare it that way.
I also understood the hash, Ok when we have attributes we create a hash for it.
The part I couldn't understand how it is converted is "item#{item.id}"
Shouldn't be some sort of "=" sign somewhere? because it was saying when we want to calculate Ruby code use that "=" ?
{:id => "item#{item.id}"} is evaluated as hash in ruby code by Haml, so you don't need =

How to render boolean data attributes with Haml?

UPDATE: Haml, indeed, already does this automatically! However, I had a hash inside of link_to helper, not a Haml tag and didn't even notice it. Silly me! So the question is pretty much invalid.
Haml makes rendering boolean HTML attributes very easy:
%input{checked: #boolean}
renders a simple <input> if #boolean is falsy, or <input checked> otherwise.
Haml also makes it easy to render data attributes:
%a{data: { is_special: false } }
renders: <a data-is-special="false">.
Is there any way to ask Haml to interpret this custom data-is-special attribute as a boolean one? I would like to not have it present if a falsy value is assigned, and for it to be present if anything truthy is assigned.
Code in brackets is normal ruby code, so if only you could perform this task in ruby, you have yourself a solution. I came up with something like that:
def remove_false(hsh)
Hash[hsh.each_pair.select {|key, value| value}]
end
{data: remove_false(is_special: false)} #=> {:data => {}}
This solution does not play well if you have combined keys that you want to treat specially and normally in single hash.

Rails 3.0.2 Array#join HTML Safe?

I have a rails gem that uses a snippet like:
components = []
components << label_for(attribute)
components << ...
components << text_field(attribute)
return components.join
The gem worked fine in Rails 3.0.1, however it escapes (renders as text in a browser) all HTML after updating to Rails 3.0.2. What am I doing something wrong? Thanks.
As #sj26 points out, either use the rails built-in helper:
<%= safe_join(components) %>
Or use my rails_join gem to make Array#join html-safe aware, in which case your original code will work as is.
String#join isn't SafeBuffer-aware.
String#html_safe marks that you string is already HTML-escaped, preventing users sneaking bits of HTML into your pages. Check out this post by Yehuda Katz on SafeBuffer and why/how you should be using them.
If you have an array of String and SafeBuffer you want to concatenate, make sure you've run #html_safe on them all, or #concat them into a SafeBuffer like so:
['<one>', '<p>two</p>'.html_safe].inject ''.html_safe, &:concat
=> "<one><p>two</p>"
Rails has a built-in helper called safe_join which will do this for you.
http://makandra.com/notes/954-don-t-mix-array-join-and-string-html_safe
class Array
def html_safe_join(delimiter='')
''.html_safe.tap do |str|
each_with_index do |element, i|
str << delimiter if i > 0
str << element
end
end
end
end
[safe_string, unsafe_string].html_safe_join(' ')
# '<span>foo</span><span&t;bar</span>'
Strings are automatically HTML-escaped in Rails3. You need to change that last line to:
return components.join.html_safe
alternately, if editing the gem is too much hassle you can do it from the view:
<%= helper_name.html_safe %>
how about manual quoting?
<%= raw ['<div>', 'any', '</div>'].map{|val| h(val)}.join('<br />') %>

What is the best way to return multiple tags from a Rails Helper?

I want to create a hidden field and create a link in one helper and then output both to my erb.
<%= my_cool_helper "something", form %>
Should out put the results of
link_to "something", a_path
form.hidden_field "something".tableize, :value => "something"
What would the definition of the helper look like? The details of what link_to and the form.hidden_field don't really matter. What matters is, how do I return the output from two different calls.
There are several ways to do this.
Remember that the existing rails helpers like link_to, etc, just output strings. You can concatenate the strings together and return that (which is what I do most of the time, if things are simple).
EG:
link_to( "something", something_path ) + #NOTE THE PLUS FOR STRING CONCAT
form.hidden_field('something'.tableize, :value=>'something')
If you're doing things which are more complicated, you could just put that code in a partial, and have your helper call render :partial.
If you're doing more complicated stuff than even that, then you may want to look at errtheblog's block_to_partial helper, which is pretty cool
So far the best I have come up with is:
def my_cool_helper(name, form)
out = capture { link_to name, a_path }
out << capture { form.hidden_field name.tableize, value => 'something' }
end
Is there a better way?
Using safe_join.
I typically prefer just concatenating with +, as shown in Orion Edwards's Answer, but here's another option I recently discovered.
safe_join( [
link_to( "something", something_path ),
form.hidden_field( "something".tableize, value: "something" )
] )
It has the advantage of explicitly listing all of the elements and the joining of those elements.
I find that with long elements, the + symbol can get lost at the end of the line. Additionally, if you're concatenating more than a few elements, I find listing them in an Array like this to be more obvious to the next reader.
If you want to buffer other output which apart from string then you can use concat instead of +.
see this - http://thepugautomatic.com/2013/06/helpers/
def output_siblings
div1 = tag.div 'some content'
div2 = tag.div 'other content'
div1 + div2
end
just simplifying some other answers in here.
This worked for me.
def format_paragraphs(text)
text.split(/\r?\n/).sum do |paragraph|
tag.p(paragraph)
end
end

Resources