I asked a similar question recently, though that was aimed at having multiple blocks as arguments. This problem is a little more immediate.
The problem I have is, I have a helper method which I want to be able to pass content as a block to render. However, if I add more than one partial only the last one in the block is rendered. The methods are below.
def bootrap_panel(title, klass = 'primary', &block)
content_tag(:div, panel_heading(title) + panel_body(&block), class: 'panel panel-' + klass)
end
def panel_body(&block)
content_tag(:div, yield, class: 'panel-body') if block_given?
end
And an example of an issue I am having is here, where only the last partial is being displayed on the page.
=bootrap_panel 'Panels', 'primary' do
- render "dynamic_panels/partials/new"
- render "dynamic_panels/partials/dynamic_panels", dynamic_panels: dynamic_page.dynamic_panels
My first thought is, well I should be yielding the partials to an array or something first, then display that (I am not sure I am able to do that). Secondly, i am using the '-' operator instead of the '=' operator for displaying ruby content in haml. Why?
Pass the block itsef instead of its return value to content_tag:
content_tag(:div, class: 'panel-body', &block)
Other method:
content_tag(:div, capture(&block), class: 'panel-body')
Related
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)
I have a view helper method that takes a block. Render that block is no problem, but when I try to add content after the block is rendered, it does not work.
def validation_div(&block)
content_tag :div do
yield
content_tag :div do
'This content is never rendered!'
end
end
end
The above code just yields the block and skips the other content. I have also tried with: content_for, capture, concat and with_output_buffer without any success. As you notice, I don't really understand how these methods work... But my question is: how to render something after the given block is rendered. Thanks!
If you look at a content_tag definition, you see it only outputs one tag, one class, and one content. You're trying to get it out out multiple contents so you'll need to join them.
def validation_div(&block)
content = capture(&block)
content_tag :div do
content + content_tag(:div , "This content is never rendered!")
end
end
While refactoring I though it'll b great to convert
%p.user-greatings #{gravatar_for #project.user} #{link_to #project.user.name, #project.user}
to helper method in order to follow DRY convention.
My raw helper method:
def assigned_user
gravatar_for yield link_to yield.name, yield
end
And my view:
%p.user-greatings #{assigned_user{#project.user}}
But my "assigned_user" displays only one part:
gravatar_for yield
How to change my method to display both parts in my view?
Calling methods while not adding braces can be confusing. Actually you are calling:
def assigned_user
gravatar_for(yield(link_to(yield.name, yield)))
end
Since your block ignores the parameters passed to it, you get something equivalent to
gravatar_for yield
I'm not sure what you tried to do, and why did you opt to bass a block rather than a simple value:
def assigned_user(user)
"#{gravatar_for user} #{link_to user.name, user}"
end
%p.user-greatings #{assigned_user(#project.user)}
I am reading the book Agile web developpment with rails 4.
there is a part where the products' cart is showing only if it is not empty, my question is the function in the view send to the helper only 2 attributes while in the implementation there are 3 parameters.
in the view I have the bellow code, which render to _cart where I have the cart show
<%= hidden_div_if(#cart.line_items.empty?, id: 'cart') do %>
<%= render #cart %>
<% end %>
the helper has:
module ApplicationHelper
def hidden_div_if(condition, attributes = {}, &block)
if condition
attributes["style"] = "display: none"
end
content_tag("div", attributes, &block) end
end
My question is the &block in this case receives id: 'cart' but is it a optional attibute? that why it comes with &. but what about attributes = {}?
I am really not sure how that is happening, could someone explain me a bit?
Thanks!!
The code between and including do and end is the block, and this is the third argument for hidden_div_if, which is simply passed on to content_tag. The & in the definition of hidden_div_if captures the block in your view, whereas the & in the call to content_tag expands it again to pass it along.
The answer here explains this idea nicely with a few examples. I recommend testing everything out yourself in irb to get a feel for it.
I'm making a style guide where I output the code on the right that is displayed on the left.
I know that adding %% escapes ERB
I have written a helper that takes the contents of a block and renders the code in two places one showing the html and I want the other to show the source ERB that created the html.
The problem is I get back HTML where I wanted ERB.
The View Code
<%= display_code do %>
<%= link_to "Button", "/style_guide, class: "btn" %>
<% end %>
The Helper Code
module StyleGuideHelper
def display_code(&block)
content = with_output_buffer(&block)
html = ""
html << content_tag(:div, content, class: "rendered-code")
html << content_tag(:div, escape_erb(content), class: "source-code-preview")
html.html_safe
end
def escape_erb(code)
code = code.gsub("%=", "%%=")
end
end
Expected Result
Button <%= link_to "Button", "/style_guide, class: "btn" %>
Actual Result
Button Button
Cheers
The issue is that this helper runs the block (link_to "Button", ...) -- it never sees the source code inside the block, just its output. You could replace escape_erb with h to capture the resulting HTML, but that won't pop back up to the ERB that generated it.
As I see it, your options are:
Break out examples into partials, then make a helper that a) renders the partial and b) displays the underlying file.
Specify your ERB fragments as strings (heredocs?), pass the string into the helper, and have the helper a) evaluate it via ERB.new(string).result(binding) to render the result and b) display the string.
Make the helper determine what part of the view invoked it, then parse the .erb well enough to find the block. Catch is, the precise format of what you see in callers is subject to change without notice due to the way views are compiled.
Make a helper that uses crazy metaprogramming juju to evaluate the block in both an ERB context as well as your own special context that intercepts the code being evaluated and turns it back into markup.
...sorted in approximate order of complexity and odds of success.
This code below will allow you to retrieve the code for a given block.
class ERBSource
ERB = ::ActionView::Template::Handlers::ERB
def self.for(block)
new(block).source
end
attr_reader :block, :file, :line_number
def initialize(block)
#block = block
#file, #line_number = *block.source_location
end
def source
lines = File.readlines(file)
relevant_lines = lines[(line_number - 1)..-1] || []
extract_first_expression(relevant_lines)
end
private
def extract_first_expression(lines)
code = lines.slice[0,1].join # add the first two lines so it has to iterate less
lines.each do |line|
code << line
return code if correct_syntax?(compile_erb(code))
end
raise SyntaxError, "unexpected $end"
end
def correct_syntax?(code)
stderr = $stderr
$stderr.reopen(IO::NULL)
RubyVM::InstructionSequence.compile(code)
$stderr.reopen(stderr)
true
rescue Exception
$stderr.reopen(stderr)
false
end
def compile_erb(code)
ERB.erb_implementation.new(
code,
:escape => false,
:trim => (ERB.erb_trim_mode == "-")
).src
end
end
This is what the helper looks like
module StyleGuideHelper
def render_example(name, &block)
code = ERBSource.for(block)
content_tag(:h2, name) +
content_tag(:div, &block) +
content_tag(:pre, content_tag(:code, code))
end
end