# frozen_string_literal: true
require "active_support/core_ext/string/output_safety"
module ActionView
# = Action View Capture Helper
module Helpers #:nodoc:
# CaptureHelper exposes methods to let you extract generated markup which
# can be used in other parts of a template or layout file.
#
# It provides a method to capture blocks into variables through capture and
# a way to capture a block of markup for use in a layout through {content_for}[rdoc-ref:ActionView::Helpers::CaptureHelper#content_for].
module CaptureHelper
# The capture method extracts part of a template as a String object.
# You can then use this object anywhere in your templates, layout, or helpers.
#
# The capture method can be used in ERB templates...
#
# <% @greeting = capture do %>
# Welcome to my shiny new web page! The date and time is
# <%= Time.now %>
# <% end %>
#
# ...and Builder (RXML) templates.
#
# @timestamp = capture do
# "The current timestamp is #{Time.now}."
# end
#
# You can then use that variable anywhere else. For example:
#
#
#
<%= @greeting %>
#
# <%= @greeting %>
#
#
#
def capture(*args)
value = nil
buffer = with_output_buffer { value = yield(*args) }
if (string = buffer.presence || value) && string.is_a?(String)
ERB::Util.html_escape string
end
end
# Calling content_for stores a block of markup in an identifier for later use.
# In order to access this stored content in other templates, helper modules
# or the layout, you would pass the identifier as an argument to content_for.
#
# Note: yield can still be used to retrieve the stored content, but calling
# yield doesn't work in helper modules, while content_for does.
#
# <% content_for :not_authorized do %>
# alert('You are not authorized to do that!')
# <% end %>
#
# You can then use content_for :not_authorized anywhere in your templates.
#
# <%= content_for :not_authorized if current_user.nil? %>
#
# This is equivalent to:
#
# <%= yield :not_authorized if current_user.nil? %>
#
# content_for, however, can also be used in helper modules.
#
# module StorageHelper
# def stored_content
# content_for(:storage) || "Your storage is empty"
# end
# end
#
# This helper works just like normal helpers.
#
# <%= stored_content %>
#
# You can also use the yield syntax alongside an existing call to
# yield in a layout. For example:
#
# <%# This is the layout %>
#
#
# My Website
# <%= yield :script %>
#
#
# <%= yield %>
#
#
#
# And now, we'll create a view that has a content_for call that
# creates the script identifier.
#
# <%# This is our view %>
# Please login!
#
# <% content_for :script do %>
#
# <% end %>
#
# Then, in another view, you could to do something like this:
#
# <%= link_to 'Logout', action: 'logout', remote: true %>
#
# <% content_for :script do %>
# <%= javascript_include_tag :defaults %>
# <% end %>
#
# That will place +script+ tags for your default set of JavaScript files on the page;
# this technique is useful if you'll only be using these scripts in a few views.
#
# Note that content_for concatenates (default) the blocks it is given for a particular
# identifier in order. For example:
#
# <% content_for :navigation do %>
# <%= link_to 'Home', action: 'index' %>
# <% end %>
#
# And in another place:
#
# <% content_for :navigation do %>
# <%= link_to 'Login', action: 'login' %>
# <% end %>
#
# Then, in another template or layout, this code would render both links in order:
#
# <%= content_for :navigation %>
#
# If the flush parameter is +true+ content_for replaces the blocks it is given. For example:
#
# <% content_for :navigation do %>
# <%= link_to 'Home', action: 'index' %>
# <% end %>
#
# <%# Add some other content, or use a different template: %>
#
# <% content_for :navigation, flush: true do %>
# <%= link_to 'Login', action: 'login' %>
# <% end %>
#
# Then, in another template or layout, this code would render only the last link:
#
# <%= content_for :navigation %>
#
# Lastly, simple content can be passed as a parameter:
#
# <% content_for :script, javascript_include_tag(:defaults) %>
#
# WARNING: content_for is ignored in caches. So you shouldn't use it for elements that will be fragment cached.
def content_for(name, content = nil, options = {}, &block)
if content || block_given?
if block_given?
options = content if content
content = capture(&block)
end
if content
options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content)
end
nil
else
@view_flow.get(name).presence
end
end
# The same as +content_for+ but when used with streaming flushes
# straight back to the layout. In other words, if you want to
# concatenate several times to the same buffer when rendering a given
# template, you should use +content_for+, if not, use +provide+ to tell
# the layout to stop looking for more contents.
def provide(name, content = nil, &block)
content = capture(&block) if block_given?
result = @view_flow.append!(name, content) if content
result unless content
end
# content_for? checks whether any content has been captured yet using content_for.
# Useful to render parts of your layout differently based on what is in your views.
#
# <%# This is the layout %>
#
#
# My Website
# <%= yield :script %>
#
#
# <%= yield %>
# <%= yield :right_col %>
#
#
def content_for?(name)
@view_flow.get(name).present?
end
# Use an alternate output buffer for the duration of the block.
# Defaults to a new empty string.
def with_output_buffer(buf = nil) #:nodoc:
unless buf
buf = ActionView::OutputBuffer.new
if output_buffer && output_buffer.respond_to?(:encoding)
buf.force_encoding(output_buffer.encoding)
end
end
self.output_buffer, old_buffer = buf, output_buffer
yield
output_buffer
ensure
self.output_buffer = old_buffer
end
end
end
end