aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/lib/action_view/helpers/capture_helper.rb
blob: 719592b5c5dcc15df020b8b86d2ff4a5b3a0ea20 (plain) (blame)
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
200
201
202
203
204
205
206
207
208
209
210
require "active_support/core_ext/string/output_safety"

module ActionView
  # = Action View Capture Helper
  module Helpers
    # 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.
    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:
      #
      #   <html>
      #   <head><title><%= @greeting %></title></head>
      #   <body>
      #   <b><%= @greeting %></b>
      #   </body>
      #   </html>
      #
      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 <tt>content_for</tt>.
      #
      # Note: <tt>yield</tt> can still be used to retrieve the stored content, but calling
      # <tt>yield</tt> doesn't work in helper modules, while <tt>content_for</tt> does.
      #
      #   <% content_for :not_authorized do %>
      #     alert('You are not authorized to do that!')
      #   <% end %>
      #
      # You can then use <tt>content_for :not_authorized</tt> anywhere in your templates.
      #
      #   <%= content_for :not_authorized if current_user.nil? %>
      #
      # This is equivalent to:
      #
      #   <%= yield :not_authorized if current_user.nil? %>
      #
      # <tt>content_for</tt>, 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 <tt>yield</tt> syntax alongside an existing call to
      # <tt>yield</tt> in a layout. For example:
      #
      #   <%# This is the layout %>
      #   <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
      #   <head>
      #     <title>My Website</title>
      #     <%= yield :script %>
      #   </head>
      #   <body>
      #     <%= yield %>
      #   </body>
      #   </html>
      #
      # And now, we'll create a view that has a <tt>content_for</tt> call that
      # creates the <tt>script</tt> identifier.
      #
      #   <%# This is our view %>
      #   Please login!
      #
      #   <% content_for :script do %>
      #     <script>alert('You are not authorized to view this page!')</script>
      #   <% 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 %>
      #     <li><%= link_to 'Home', action: 'index' %></li>
      #   <% end %>
      #
      #  And in another place:
      #
      #   <% content_for :navigation do %>
      #     <li><%= link_to 'Login', action: 'login' %></li>
      #   <% end %>
      #
      # Then, in another template or layout, this code would render both links in order:
      #
      #   <ul><%= content_for :navigation %></ul>
      #
      # If the flush parameter is true content_for replaces the blocks it is given. For example:
      #
      #   <% content_for :navigation do %>
      #     <li><%= link_to 'Home', action: 'index' %></li>
      #   <% end %>
      #
      #   <%#  Add some other content, or use a different template: %>
      #
      #   <% content_for :navigation, flush: true do %>
      #     <li><%= link_to 'Login', action: 'login' %></li>
      #   <% end %>
      #
      # Then, in another template or layout, this code would render only the last link:
      #
      #   <ul><%= content_for :navigation %></ul>
      #
      # 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 %>
      #   <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
      #   <head>
      #     <title>My Website</title>
      #     <%= yield :script %>
      #   </head>
      #   <body class="<%= content_for?(:right_col) ? 'two-column' : 'one-column' %>">
      #     <%= yield %>
      #     <%= yield :right_col %>
      #   </body>
      #   </html>
      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