1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
# frozen_string_literal: true
require 'sinatra/base'
require 'sinatra/capture'
module Sinatra
# = Sinatra::ContentFor
#
# <tt>Sinatra::ContentFor</tt> 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 %>
# <chunk of="html">...</chunk>
# <% 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 %>
# <chunk of="default html">...</chunk>
# <% 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 <tt><%= yield_content :scripts_and_styles %></tt> on your
# layout, inside the <head> tag, and each view can call <tt>content_for</tt>
# setting the appropriate set of tags that should be added to the layout.
#
# == Limitations
#
# Due to the rendering process limitation using <tt><%= yield_content %></tt>
# from within nested templates do not work above the <tt><%= 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 %>
# <script>
# alert('one');
# </script>
# <% end %>
# <% content_for :two do %>
# <script>
# alert('two');
# </script>
# <% end %>
#
# Using <tt><%= yield_content %></tt> before <tt><%= yield %></tt> 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 %>
# <script type="text/javascript" src="/foo.js"></script>
# <% 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 <tt>yield_content</tt>
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 %>
# <script type="text/javascript" src="/foo.js"></script>
# <% end %>
#
# <% if content_for? :head %>
# <span>content "head" was defined.</span>
# <% 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:
#
# <head>
# <title>Example</title>
# <%= yield_content :head %>
# </head>
#
# Would render everything you declared with <tt>content_for
# :head</tt> before closing the <tt><head></tt> 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 <tt>1</tt> and <tt>2</tt> to all the blocks registered
# for <tt>:head</tt>.
def yield_content(key, *args, &block)
if block_given? && !content_for?(key)
haml? && Tilt[:haml] == Tilt::HamlTemplate && respond_to?(:capture_haml) ? 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
|