Appending to yield in content_tag - ruby-on-rails

Lets say I have this helper in application_helper.rb
def my_helper(content = nil, *args, &block)
content_tag(:div, class: :my_wrapper) do
(block_given? ? yield : content) + content_tag(:span, "this is the end", *args)
end
end
And i call it from a view with
my_helper do
content_tag(:div, "this is the beginning")
end
I would expect the result to be something like
<div class="my_wrapper">
<div>
this it the beginning
</div>
<span>
this is the end
</span>
</div>
But in fact, the span with the text "this is the end" will not be appended to the yield.
If i were to use this line in the helper instead:
(block_given? ? content_tag(:div, &block) : content) + content_tag(:span, "this is the end", *args)
I would get both content, but the yield would be wrapped inside another div.
How could I add / append content after a yield, without wrapping the yield in a different content_tag?

You can use capture to achieve this:
def my_helper(content = nil, *args, &block)
content_tag(:div, class: :my_wrapper) do
(block_given? ? capture(&block) : content) + content_tag(:span, "this is the end", *args)
end
end
And be sure to do this in your view:
<%= my_helper do %>
<%= content_tag(:div, "this is the beginning") %>
<%- end %>

Related

Render html from helper in RoR

I have a method in a helper, here is the code:
def top_menu_output(parent = Category.where(parent_id: nil))
parent.each do |p|
content_tag(:li) do
parent_id = p.id
if Category.where(parent_id: parent_id).exists?
link_to(p.title, p.page_name, class: "parent")
content_tag(:ul, class: "unstyled") do
subparent = Category.where(parent_id: parent_id)
content_tag(:li) do
top_menu_output(subparent)
end
end
elsif
link_to(p.title, p.page_name)
end
end
end
end
and I call the method in a view like this
<%top_menu_output%>
but it renders nothing. What do I do to render all the links and li's? Thank you for your answers.
P.S. If I put the code right in the view it works just fine, but the view is obviously not the right place for the method.
P.P.S. If I call the method like this <%=top_menu_output%> it renders all the stuff from my db CATEGORY ID: 1, PARENT_ID: NIL, PAGE_NAME: "", KEYWORDS: "", DESCRIPTION: "", SEO_TEXT_LEFT: "", SEO_TEXT_RIGHT: "", CREATED_AT: "2014-04-19 22:08:55", UPDATED_AT: "2014-04-19 22:08:55"...
The return value of each is the collection that is iterated over, so you are generating a whole bunch of html but then returning something else
You could change that to
parent.collect do |p|
content_tag(:li) do
...
end
end.join
This would collect the html generated by each iteration through the block, concatenate them all and return that. As pointed out in the comments, you also need to use <%= or you'll never see any output
Thanks Frederick, changing each to collect is almost what I want. But it is also necessary to use concat and + so the properly working method is:
def top_menu_output(parent = Category.where(parent_id: nil))
parent.collect do |p|
content_tag(:li) do
parent_id = p.id
if Category.where(parent_id: parent_id).exists?
link_to(p.title, p.page_name, class: "parent") +
content_tag(:ul, class: "unstyled js-menu") do
subparent = Category.where(parent_id: parent_id)
top_menu_output(subparent)
end
elsif
concat link_to(p.title, p.page_name)
end
end
end.join.html_safe
end

content_tag behavior different in view and helper

Someone can explain me why content_tag have a different behavior in view and helpers? A simple code like the one below returns (1) when in the view, but (2) when in the Helper.
arr = ["a", "b", "c"]
concat content_tag(:ul, :class => 'a class') do
arr.each do |item|
concat content_tag(:li, item)
end
end
(1)
<ul class="a class">
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
(2)
<ul>{:class=>"a class"}</ul>
When using content_tag(:ul, '', :class => 'a class') in the Helper, it renders <ul class="a class"></ul>. Why this different behavior too?
The content_tag's behavior is very confusing. I tried some magic with the capture (another confusing method!) method, but without any success.
So... any ideas?
PS.: I'm using rails 4
-------------edit--------------
Since my example is really bad, what I'm really trying to do is somethind like that:
def language_dropdown
content_tag :li, class: "dropdown language" do
concat content_tag(:a, content_tag(:span, I18n.locale, class: "username"),
class: "dropdown-toggle", data: { toggle: "dropdown", hover: "dropdown", "close-others" => "true" } )
concat( content_tag(:ul, class: "dropdown-menu") do
I18n.available_locales.each do |locale|
if locale != I18n.locale
locale_key = "translation.#{locale}"
content_tag :li do
concat(link_to I18n.t(locale_key), url_for(locale: locale.to_s))
end
end
end
end)
end.html_safe
end
This works form me...
module ApplicationHelper
def some_helper arr
content_tag(:ul, :class => 'a class') do
arr.each do |item|
concat(content_tag(:li, item))
end
end
end
end
Remove concat from first content_tag and add brackets into the inner concat method.
Update:
content_tag :li, class: "dropdown language" do
concat(content_tag(:a,content_tag(:span, I18n.locale, class: "username"),
class: "dropdown-toggle", data: { toggle: "dropdown", hover: "dropdown", "close-others" => "true" }))
concat(content_tag(:ul, class: "dropdown-menu"){
I18n.available_locales.each do |locale|
if locale != I18n.locale
locale_key = "translation.#{locale}"
concat(content_tag(:li){link_to(I18n.t(locale_key), url_for(locale: locale.to_s))})
end
end
})
end
The same thing, you need to add brackets and remove the first concat to your code...
Update 2:
items = ""
I18n.available_locales.each do |locale|
if locale != I18n.locale
locale_key = "translation.#{locale}"
items += content_tag(:li) do
link_to(I18n.t(locale_key), url_for(locale: locale.to_s))
end
end
end
label = content_tag(:span, I18n.locale, class: "username")
link = content_tag(:a, label, class: "dropdown-toggle",
data: { toggle: "dropdown", hover: "dropdown", "close-others" => "true" })
content_tag :li, class: "dropdown language" do
concat(link)
concat(content_tag(:ul, class: "dropdown-menu"){ items.html_safe })
end

How to present list of names in Presenter?

I have my custom presenter
class ShiftPresenter
def initialize(shift, template)
#shift = shift
#template = template
end
def h
#template
end
def users_list
logs = ShiftLog.by_shift(#shift)
names = logs.map do |log|
log.cardiologist.name
end
h.content_tag :div, names unless names.empty?
end
end
and #index view
- present shift do |shift_presenter|
= shift_presenter.user_list
How to present users names using li instead of ['tom', 'jerry']
You can add this to your presenter method:
def users_list
logs = ShiftLog.by_shift(#shift)
names = logs.map(&:cardiologist).map(&:name)#.compact.uniq # you can add this if you want
h.content_tag :div do
h.content_tag :ul do
ul_content = ''.html_safe
names.each do |name|
ul_content << h.content_tag :li, name
end
ul_content
end
end
The thing is it works as block with the return statement: the last used/returned object will be put inside the content_tag.
Try to wrap each element of names in users_list method into <li> tag and join them in a string. To do this you need to change this line:
h.content_tag :div, names unless names.empty?
into this:
h.content_tag :div, names.map{|str| '<li>' + str + '</li>'}.join unless names.empty?

blocks in views and refactor in rails

I have notes attribute in Product model with text "something, something else".
In views I wanted see:
<div>
<span>Something</span>
<span>Something else</span>
</div>
Also I have working code, but I want refactor with decorator(draper) or maybe use helpers.
%div
- product.notes.split(/,/).each do |e|
%span= e.strip.capitalize
In decorator:
def notes_list
model.notes.split(/,/).each do |e|
h.content_tag(:span, e.strip.capitalize)
end
end
In views:
%div
= product.notes_list
(or analog in helpers:
def notes_list(product)
product.notes.split(/,/).each do |element|
content_tag(:span, element.strip.capitalize)
end
end
call:
%div
= notes_list(product)
)
But this returns
<div>
"
["something", " something else"]
"
</div>
What is wrong?
your notes_list is returning product.notes.split(/,/)
Try
def notes_list(product)
result = product.notes.split(/,/).inject([]) do |result, element|
result << content_tag(:span, element.strip.mb_chars.capitalize)
end
result.join("\n")
end

block methods in ruby

I am using a block method to print a list, but it is generating error.
class MyDataListBuilder
attr_accessor :object
def initialize(object)
#object = object
end
def column (&block)
content_tag :li, block.call
end
end
and using it as
<%= my_data_list_for #leads, [" :10", "Age:30", "Contact:140", "Phone:140", "Email:180", "Company:100", ""] do |l| %>
<%= l.column do %>
<%= object.age %>
<% end %>
<% end %>
other methods are
def list_headers(args=[])
args = Array.new(args)
columns = []
args.map { |o| columns << content_tag(:li, o.split(":").first, :style=>"width:#{o.split(":").second}px;") }
content_tag(:ul, columns.join(" ").html_safe, :class=>"list-headers")
end
def my_data_list_for(object, headers=[], &block)
arr = []
object.each do |o|
arr = capture(DataListHelper::MyDataListBuilder.new(o), &block)
end
content_tag(:ol, list_headers(headers) + arr, :class=>"data-list")
end
it is generating an error and i can not figure out why:
ActionView::Template::Error (undefined local variable or method `object' for #<#<Class:0xcaa1ca0>:0xca9ebf4>):
Please help me in it.
This solves the issue.
class MyDataListBuilder
include ActionView::Helpers::TagHelper
include ActionView::Helpers::CaptureHelper
attr_accessor :object, :output_buffer
def initialize(object)
#object = object
#output_buffer = nil
end
def column (&block)
if block_given?
content_tag(:li, capture(self, &block))
else
content_tag(:li, "")
end
end
end

Resources