partials.rb 11.6 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
module ActionView
R
Ryan Bates 已提交
2 3 4 5
  # There's also a convenience method for rendering sub templates within the current controller that depends on a
  # single object (we call this kind of sub templates for partials). It relies on the fact that partials should
  # follow the naming convention of being prefixed with an underscore -- as to separate them from regular
  # templates that could be rendered on their own.
6 7 8
  #
  # In a template for Advertiser#account:
  #
9
  #  <%= render :partial => "account" %>
10
  #
R
Ryan Bates 已提交
11 12
  # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable
  # +account+ to the template for display.
13 14 15
  #
  # In another template for Advertiser#buy, we could have:
  #
16
  #   <%= render :partial => "account", :locals => { :account => @buyer } %>
D
Initial  
David Heinemeier Hansson 已提交
17 18
  #
  #   <% for ad in @advertisements %>
19
  #     <%= render :partial => "ad", :locals => { :ad => ad } %>
D
Initial  
David Heinemeier Hansson 已提交
20 21
  #   <% end %>
  #
R
Ryan Bates 已提交
22 23
  # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then
  # render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display.
D
Initial  
David Heinemeier Hansson 已提交
24 25 26
  #
  # == Rendering a collection of partials
  #
R
Ryan Bates 已提交
27 28 29 30
  # The example of partial use describes a familiar pattern where a template needs to iterate over an array and
  # render a sub template for each of the elements. This pattern has been implemented as a single method that
  # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined
  # example in "Using partials" can be rewritten with a single line:
D
Initial  
David Heinemeier Hansson 已提交
31
  #
32
  #   <%= render :partial => "ad", :collection => @advertisements %>
D
Initial  
David Heinemeier Hansson 已提交
33
  #
R
Ryan Bates 已提交
34 35 36
  # This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An
  # iteration counter will automatically be made available to the template with a name of the form
  # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
37
  #
R
Ryan Bates 已提交
38 39
  # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
  # just keep domain objects, like Active Records, in there.
40
  #
D
Initial  
David Heinemeier Hansson 已提交
41 42 43 44
  # == Rendering shared partials
  #
  # Two controllers can share a set of partials and render them like this:
  #
45
  #   <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
D
Initial  
David Heinemeier Hansson 已提交
46
  #
47
  # This will render the partial "advertisement/_ad.erb" regardless of which controller this is being called from.
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
  # == Rendering objects with the RecordIdentifier
  #
  # Instead of explicitly naming the location of a partial, you can also let the RecordIdentifier do the work if
  # you're following its conventions for RecordIdentifier#partial_path. Examples:
  #
  #  # @account is an Account instance, so it uses the RecordIdentifier to replace
  #  # <%= render :partial => "accounts/account", :locals => { :account => @buyer } %>
  #  <%= render :partial => @account %>
  #
  #  # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
  #  # <%= render :partial => "posts/post", :collection => @posts %>
  #  <%= render :partial => @posts %>
  #
  # == Rendering the default case
  #
  # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
  # defaults of render to render partials. Examples:
  #
  #  # Instead of <%= render :partial => "account" %>
  #  <%= render "account" %>
  #
  #  # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
  #  <%= render "account", :account => @buyer %>
  #
  #  # @account is an Account instance, so it uses the RecordIdentifier to replace
  #  # <%= render :partial => "accounts/account", :locals => { :account => @account } %>
  #  <%= render(@account) %>
  #
  #  # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
  #  # <%= render :partial => "posts/post", :collection => @posts %>
  #  <%= render(@posts) %>
  #
81 82
  # == Rendering partials with layouts
  #
R
Ryan Bates 已提交
83 84 85
  # Partials can have their own layouts applied to them. These layouts are different than the ones that are
  # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
  # of users:
86
  #
87
  #   <%# app/views/users/index.html.erb &>
88 89 90 91 92 93
  #   Here's the administrator:
  #   <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %>
  #
  #   Here's the editor:
  #   <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %>
  #
94
  #   <%# app/views/users/_user.html.erb &>
95 96
  #   Name: <%= user.name %>
  #
97
  #   <%# app/views/users/_administrator.html.erb &>
98 99 100 101 102
  #   <div id="administrator">
  #     Budget: $<%= user.budget %>
  #     <%= yield %>
  #   </div>
  #
103
  #   <%# app/views/users/_editor.html.erb &>
104
  #   <div id="editor">
105
  #     Deadline: <%= user.deadline %>
106 107 108 109 110 111 112 113 114 115 116 117 118
  #     <%= yield %>
  #   </div>
  #
  # ...this will return:
  #
  #   Here's the administrator:
  #   <div id="administrator">
  #     Budget: $<%= user.budget %>
  #     Name: <%= user.name %>
  #   </div>
  #
  #   Here's the editor:
  #   <div id="editor">
119
  #     Deadline: <%= user.deadline %>
120 121 122 123 124
  #     Name: <%= user.name %>
  #   </div>
  #
  # You can also apply a layout to a block within any template:
  #
125
  #   <%# app/views/users/_chief.html.erb &>
126 127 128 129 130 131 132 133 134 135 136
  #   <% render(:layout => "administrator", :locals => { :user => chief }) do %>
  #     Title: <%= chief.title %>
  #   <% end %>
  #
  # ...this will return:
  #
  #   <div id="administrator">
  #     Budget: $<%= user.budget %>
  #     Title: <%= chief.name %>
  #   </div>
  #
137
  # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
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
  #
  # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
  # an array to layout and treat it as an enumerable.
  #
  #   <%# app/views/users/_user.html.erb &>
  #   <div class="user">
  #     Budget: $<%= user.budget %>
  #     <%= yield user %>
  #   </div>
  #
  #   <%# app/views/users/index.html.erb &>
  #   <% render :layout => @users do |user| %>
  #     Title: <%= user.title %>
  #   <% end %>
  #
  # This will render the layout for each user and yield to the block, passing the user, each time.
  #
  # You can also yield multiple times in one layout and use block arguments to differentiate the sections.
  #
  #   <%# app/views/users/_user.html.erb &>
  #   <div class="user">
  #     <%= yield user, :header %>
  #     Budget: $<%= user.budget %>
  #     <%= yield user, :footer %>
  #   </div>
  #
  #   <%# app/views/users/index.html.erb &>
  #   <% render :layout => @users do |user, section| %>
  #     <%- case section when :header -%>
  #       Title: <%= user.title %>
  #     <%- when :footer -%>
  #       Deadline: <%= user.deadline %>
  #     <%- end -%>
  #   <% end %>
D
Initial  
David Heinemeier Hansson 已提交
172
  module Partials
173
    extend ActiveSupport::Concern
Y
Yehuda Katz 已提交
174

175
    class PartialRenderer
176 177
      PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
      TEMPLATES = Hash.new {|h,k| h[k] = {} }
178

179
      attr_reader :template
180

181 182
      def initialize(view_context, options, block)
        @view           = view_context
183
        @partial_names  = PARTIAL_NAMES[@view.controller.class]
J
Joshua Peek 已提交
184

185 186
        key = Thread.current[:format_locale_key]
        @templates      = TEMPLATES[key] if key
J
Joshua Peek 已提交
187

188 189
        setup(options, block)
      end
J
Joshua Peek 已提交
190

191 192
      def setup(options, block)
        partial = options[:partial]
J
Joshua Peek 已提交
193

194 195 196
        @options = options
        @locals  = options[:locals] || {}
        @block   = block
J
Joshua Peek 已提交
197

198
        if String === partial
199 200 201
          @object     = options[:object]
          @path       = partial
          @collection = collection
202 203
        else
          @object = partial
204 205 206 207 208 209 210

          if @collection = collection
            paths = @collection_paths = @collection.map { |o| partial_path(o) }
            @path = paths.uniq.size == 1 ? paths.first : nil
          else
            @path = partial_path
          end
211
        end
212 213
      end

214
      def render
215
        identifier = ((@template = find_template) ? @template.identifier : @path)
216

217
        if @collection
218
          ActiveSupport::Notifications.instrument("action_view.render_collection",
219
            :identifier => identifier, :count => @collection.size) do
220 221
            render_collection
          end
222
        else
223
          content = ActiveSupport::Notifications.instrument("action_view.render_partial",
224
            :identifier => identifier) do
225 226 227
            render_partial
          end

228 229
          if !@block && (layout = @options[:layout])
            content = @view._render_layout(find_template(layout), @locals){ content }
230 231
          end
          content
232
        end
233
      end
D
Initial  
David Heinemeier Hansson 已提交
234

235
      def render_collection
236
        return nil if @collection.blank?
Y
Yehuda Katz 已提交
237

238
        if @options.key?(:spacer_template)
239
          spacer = find_template(@options[:spacer_template]).render(@view, @locals)
240
        end
241

242
        result = @template ? collection_with_template : collection_without_template
243
        result.join(spacer).html_safe!
244 245
      end

246
      def collection_with_template(template = @template)
247
        segments, locals, as = [], @locals, @options[:as] || template.variable_name
248

249 250
        counter_name  = template.counter_name
        locals[counter_name] = -1
251

252
        @collection.each do |object|
253
          locals[counter_name] += 1
254
          locals[as] = object
255

256
          segments << template.render(@view, locals)
257
        end
J
Joshua Peek 已提交
258

259
        @template = template
260
        segments
261
      end
Y
Yehuda Katz 已提交
262

263
      def collection_without_template(collection_paths = @collection_paths)
264
        segments, locals, as = [], @locals, @options[:as]
265 266
        index, template = -1, nil

J
José Valim 已提交
267 268
        @collection.each_with_index do |object, i|
          template = find_template(collection_paths[i])
269
          locals[template.counter_name] = (index += 1)
270
          locals[as || template.variable_name] = object
271

272
          segments << template.render(@view, locals)
273
        end
274

275
        @template = template
276
        segments
277 278
      end

279 280
      def render_partial(object = @object)
        locals, view = @locals, @view
281

282 283
        object ||= locals[template.variable_name]
        locals[@options[:as] || template.variable_name] = object
284

285 286
        template.render(view, locals) do |*name|
          view._layout_for(*name, &@block)
287 288
        end
      end
289

290 291
    private
      def collection
292
        if @object.respond_to?(:to_ary)
293 294 295 296
          @object
        elsif @options.key?(:collection)
          @options[:collection] || []
        end
297
      end
298

299
      def find_template(path = @path)
300 301 302 303
        unless @templates
          path && _find_template(path)
        else
          path && @templates[path] ||= _find_template(path)
304
        end
305
      end
J
Joshua Peek 已提交
306

307
      def _find_template(path)
Y
Yehuda Katz 已提交
308 309 310 311
        if controller = @view.controller
          prefix = controller.controller_path unless path.include?(?/)
        end

312 313
        @view.find(path, {:formats => @view.formats}, prefix, true)
      end
314 315

      def partial_path(object = @object)
316
        @partial_names[object.class] ||= begin
317
          object = object.to_model if object.respond_to?(:to_model)
318

319
          object.class.model_name.partial_path.dup.tap do |partial|
320 321
            path = @view.controller_path
            partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/)
322 323 324 325 326 327
          end
        end
      end
    end

    def render_partial(options)
Y
Yehuda Katz 已提交
328
      _evaluate_assigns_and_ivars
329 330

      details = options[:_details]
J
Joshua Peek 已提交
331

332 333 334 335 336 337
      # Is this needed
      self.formats = details[:formats] if details
      renderer = PartialRenderer.new(self, options, nil)
      text = renderer.render
      options[:_template] = renderer.template
      text
338 339 340
    end

    def _render_partial(options, &block) #:nodoc:
J
Joshua Peek 已提交
341
      if defined? @renderer
342 343 344 345
        @renderer.setup(options, block)
      else
        @renderer = PartialRenderer.new(self, options, block)
      end
J
Joshua Peek 已提交
346

347
      @renderer.render
348 349
    end

D
Initial  
David Heinemeier Hansson 已提交
350 351
  end
end