aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/helpers/capture_helper.rb
blob: 7cf2b02659eeea365695f7eb2a2a5f83d7df07fa (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
module ActionView
  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 code for use in a layout through content_for.
    module CaptureHelper
      # The capture method allows you to extract a part of the template into a 
      # variable. You can then use this value anywhere in your templates or layout. 
      # 
      # ==== Examples
      # The capture method can be used in RHTML (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 the content as a variable anywhere else.  For
      # example:
      #
      #   <html>
      #   <head><title><%= @greeting %></title></head>
      #   <body>
      #   <b><%= @greeting %></b>
      #   </body></html>
      #
      def capture(*args, &block)
        # execute the block
        begin
          buffer = eval(ActionView::Base.erb_variable, block.binding)
        rescue
          buffer = nil
        end
        
        if buffer.nil?
          capture_block(*args, &block).to_s
        else
          capture_erb_with_buffer(buffer, *args, &block).to_s
        end
      end
      
      # Calling content_for stores the block of markup in an identifier for later use.
      # You can make subsequent calls to the stored content in another template or in the layout
      # by calling it by name with <tt>yield</tt>.
      # 
      # ==== Examples
      # 
      #   <% content_for("authorized") do %>
      #     alert('You are not authorized for that!')
      #   <% end %>
      #
      # You can then use <tt>yield :authorized</tt> anywhere in your templates.
      #
      #   <%= yield :authorized if current_user == nil %>
      #
      # You can also use these variables 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 content_for call that
      # creates the <tt>script</tt> identifier.
      #
      #   <!-- This is our view -->
      #   Please login!
      #
      #   <% content_for("script") do %>
      #     <script type="text/javascript">alert('You are not authorized for this action!')</script>
      #   <% end %>
      #
      # Then in another view you may want to do something like this:
      #
      #   <%= link_to_remote 'Logout', :action => 'logout' %>
      #
      #   <% content_for("script") do %>
      #     <%= javascript_include_tag :defaults %>
      #   <% end %>
      #
      # That will include Prototype and Scriptaculous into the page; this technique
      # is useful if you'll only be using these scripts on a few views.
      #
      # NOTE: Beware that content_for is ignored in caches. So you shouldn't use it
      # for elements that are going to be fragment cached.
      #
      # The deprecated way of accessing a content_for block was to use a instance variable
      # named @@content_for_#{name_of_the_content_block}@. So <tt><%= content_for('footer') %></tt>
      # would be avaiable as <tt><%= @content_for_footer %></tt>. The preferred notation now is
      # <tt><%= yield :footer %></tt>.
      def content_for(name, content = nil, &block)
        eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
      end

      private
        def capture_block(*args, &block)
          block.call(*args)
        end
      
        def capture_erb(*args, &block)
          buffer = eval(ActionView::Base.erb_variable, block.binding)
          capture_erb_with_buffer(buffer, *args, &block)
        end
      
        def capture_erb_with_buffer(buffer, *args, &block)
          pos = buffer.length
          block.call(*args)
        
          # extract the block 
          data = buffer[pos..-1]
        
          # replace it in the original with empty string
          buffer[pos..-1] = ''
        
          data
        end
      
        def erb_content_for(name, &block)
          eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_erb(&block)"
        end
      
        def block_content_for(name, &block)
          eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_block(&block)"
        end
    end
  end
end