# frozen_string_literal: true
require 'sinatra/base'
require 'sinatra/capture'
module Sinatra
# = Sinatra::ContentFor
#
# Sinatra::ContentFor is a set of helpers that allows you to capture
# blocks inside views to be rendered later during the request. The most
# common use is to populate different parts of your layout from your view.
#
# The currently supported engines are: Erb, Erubi, Haml and Slim.
#
# == Usage
#
# You call +content_for+, generally from a view, to capture a block of markup
# giving it an identifier:
#
# # index.erb
# <% content_for :some_key do %>
# ...
# <% end %>
#
# Then, you call +yield_content+ with that identifier, generally from a
# layout, to render the captured block:
#
# # layout.erb
# <%= yield_content :some_key %>
#
# If you have provided +yield_content+ with a block and no content for the
# specified key is found, it will render the results of the block provided
# to yield_content.
#
# # layout.erb
# <% yield_content :some_key_with_no_content do %>
# ...
# <% end %>
#
# === Classic Application
#
# To use the helpers in a classic application all you need to do is require
# them:
#
# require "sinatra"
# require "sinatra/content_for"
#
# # Your classic application code goes here...
#
# === Modular Application
#
# To use the helpers in a modular application you need to require them, and
# then, tell the application you will use them:
#
# require "sinatra/base"
# require "sinatra/content_for"
#
# class MyApp < Sinatra::Base
# helpers Sinatra::ContentFor
#
# # The rest of your modular application code goes here...
# end
#
# == And How Is This Useful?
#
# For example, some of your views might need a few javascript tags and
# stylesheets, but you don't want to force this files in all your pages.
# Then you can put <%= yield_content :scripts_and_styles %> on your
# layout, inside the
tag, and each view can call content_for
# setting the appropriate set of tags that should be added to the layout.
#
# == Limitations
#
# Due to the rendering process limitation using <%= yield_content %>
# from within nested templates do not work above the <%= yield %> statement.
# For more details https://github.com/sinatra/sinatra-contrib/issues/140#issuecomment-48831668
#
# # app.rb
# get '/' do
# erb :body, :layout => :layout do
# erb :foobar
# end
# end
#
# # foobar.erb
# <% content_for :one do %>
#
# <% end %>
# <% content_for :two do %>
#
# <% end %>
#
# Using <%= yield_content %> before <%= yield %> will cause only the second
# alert to display:
#
# # body.erb
# # Display only second alert
# <%= yield_content :one %>
# <%= yield %>
# <%= yield_content :two %>
#
# # body.erb
# # Display both alerts
# <%= yield %>
# <%= yield_content :one %>
# <%= yield_content :two %>
#
module ContentFor
include Capture
# Capture a block of content to be rendered later. For example:
#
# <% content_for :head do %>
#
# <% end %>
#
# You can also pass an immediate value instead of a block:
#
# <% content_for :title, "foo" %>
#
# You can call +content_for+ multiple times with the same key
# (in the example +:head+), and when you render the blocks for
# that key all of them will be rendered, in the same order you
# captured them.
#
# Your blocks can also receive values, which are passed to them
# by yield_content
def content_for(key, value = nil, options = {}, &block)
block ||= proc { |*| value }
clear_content_for(key) if options[:flush]
content_blocks[key.to_sym] << capture_later(&block)
end
# Check if a block of content with the given key was defined. For
# example:
#
# <% content_for :head do %>
#
# <% end %>
#
# <% if content_for? :head %>
# content "head" was defined.
# <% end %>
def content_for?(key)
content_blocks[key.to_sym].any?
end
# Unset a named block of content. For example:
#
# <% clear_content_for :head %>
def clear_content_for(key)
content_blocks.delete(key.to_sym) if content_for?(key)
end
# Render the captured blocks for a given key. For example:
#
#
# Example
# <%= yield_content :head %>
#
#
# Would render everything you declared with content_for
# :head before closing the tag.
#
# You can also pass values to the content blocks by passing them
# as arguments after the key:
#
# <%= yield_content :head, 1, 2 %>
#
# Would pass 1 and 2 to all the blocks registered
# for :head.
def yield_content(key, *args, &block)
if block_given? && !content_for?(key)
haml? && Tilt[:haml] == Tilt::HamlTemplate ? capture_haml(*args, &block) : yield(*args)
else
content = content_blocks[key.to_sym].map { |b| capture(*args, &b) }
content.join.tap do |c|
if block_given? && (erb? || erubi?)
@_out_buf << c
end
end
end
end
private
def content_blocks
@content_blocks ||= Hash.new { |h, k| h[k] = [] }
end
end
helpers ContentFor
end