require "pathname"
require "active_support/concern"
module Machined
module Helpers
module RenderHelpers
extend ActiveSupport::Concern
include LocalsHelpers
# This is the short form of both #render_partial and #render_collection.
# It works exactly like #render_partial, except if you pass the
# +:collection+ option:
#
# <%= render "ad", :collection => advertisements %>
# # is the same as:
# <%= render_collection advertisements, "ad" %>
#
def render(partial, options = {})
if collection = options.delete(:collection)
render_collection collection, partial, options
else
render_partial partial, options
end
end
# Renders the given +collection+ of objects with the given
# +partial+ template. This follows the same conventions
# of Rails' partial rendering, where the individual objects
# will be set as local variables based on the name of the partial:
#
# <%= render_collection advertisements, "ad" %>
#
# This will render the "ad" template 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+.
def render_collection(collection, partial, options = {})
return if collection.nil? || collection.empty?
template = resolve_partial partial
counter = 0
collection.inject('') do |output, object|
counter += 1
output << render_partial(template, options.merge(:object => object, :counter => counter))
end
end
# Renders a single +partial+. The primary options are:
#
# * :locals - A hash of local variables to use when
# rendering the partial.
# * :object - The object rendered in the partial.
# * :as - The name of the object to use.
#
# == Some Examples
#
# <%= render_partial "account" %>
#
# This will look for a template in the views paths with the name
# "account" or "_account". The files can be any processable Tilt
# template files, like ".erb", ".md", or ".haml" - or just plain ".html".
#
# <%= render_partial "account", :locals => { :account => buyer } %>
#
# This will set `buyer` as a local variable named "account". This can
# actually be written a few different ways:
#
# <%= render_partial "account", :account => buyer %>
# # Leftover options are assumed to be locals.
# <%= render_partial "account", :object => buyer %>
# # The local variable name "account" is inferred.
#
# As mentioned above, any options that are not used by #render_partial
# are assumed to be locals when the +:locals+ option is not set.
#
# Also mentioned above, the +:object+ option works like in Rails,
# where the local variable name will be inferred from the partial name.
# This can be overridden with the +:as+ option:
#
# <%= render_partial "account", :object => buyer, :as => "user" %>
#
# This is equivalent to:
#
# <%= render_partial "account", :locals => { :user => buyer } %>
#
def render_partial(partial, options = {})
template = resolve_partial partial
depend_on template
partial_locals = {}
# Temporarily use a different layout (default to no layout)
partial_locals[:layout] = options.delete(:layout) || false
# Add object with the name of the partial
# as the local variable name.
if object = options.delete(:object)
object_name = options.delete(:as) || template.to_s[/_?(\w+)(\.\w+)*$/, 1]
partial_locals[object_name] = object
partial_locals["#{object_name}_counter"] = options.delete :counter
end
# Add locals from leftover options
if leftover_locals = options.delete(:locals) || options
partial_locals.merge! leftover_locals
end
# Now evaluate the partial
with_locals(partial_locals) { return evaluate template }
end
protected
# Attempts to find a view with the given path,
# while also looking for a version with a partial-style
# name (prefixed with an "_").
def resolve_partial(path) # :nodoc:
path = Pathname.new path
path.absolute? and return path
# First look for the normal path
machined.views.resolve(path) { |found| return found }
# Then look for the partial-style version
unless path.basename.to_s =~ /^_/
partial = path.dirname.join "_#{path.basename}"
machined.views.resolve(partial) { |found| return found }
end
raise Sprockets::FileNotFound, "couldn't find file '#{path}'"
end
end
end
end