/
content_for.rb
199 lines (189 loc) · 5.82 KB
/
content_for.rb
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
198
199
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, Erubis, 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)
key = key.to_sym
block ||= proc { |*| value }
clear_content_for(key) if options[:flush]
content_blocks[key] << 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)
key = key.to_sym
if block_given? && !content_for?(key)
haml? ? capture_haml(*args, &block) : yield(*args)
else
content = content_blocks[key].map { |b| capture(*args, &b) }
content.join.tap do |c|
if block_given? && (erb? || erubi? || erubis?)
@_out_buf << c
end
end
end
end
private
def content_blocks
@content_blocks ||= Hash.new { |h, k| h[k] = [] }
end
end
helpers ContentFor
end