module Blocks
class Base
# a pointer to the ActionView that called Blocks
attr_accessor :view
# Hash of block names to Blocks::Container objects
attr_accessor :blocks
# Array of Blocks::Container objects, storing the order of blocks as they were queued
attr_accessor :queued_blocks
# counter, used to give unnamed blocks a unique name
attr_accessor :anonymous_block_number
# A Hash of queued_blocks arrays; a new array is started when method_missing is invoked
attr_accessor :block_groups
# These are the options that are passed into the initalize method
attr_accessor :global_options
# The default folder to look in for global partials
attr_accessor :template_folder
# The variable to use when rendering the partial for the templating feature (by default, "blocks")
attr_accessor :variable
# Boolean variable for whether Blocks should attempt to render blocks as partials if a defined block cannot be found
attr_accessor :use_partials
# Boolean variable for whether Blocks should render before and after blocks inside or outside of a collections' elements' surrounding tags
attr_accessor :surrounding_tag_surrounds_before_and_after_blocks
# Checks if a particular block has been defined within the current block scope.
# <%= blocks.defined? :some_block_name %>
# Options:
# [+name+]
# The name of the block to check
def defined?(name)
!blocks[name.to_sym].nil?
end
# Define a block, unless a block by the same name is already defined.
# <%= blocks.define :some_block_name, :parameter1 => "1", :parameter2 => "2" do |options| %>
# <%= options[:parameter1] %> and <%= options[:parameter2] %>
# <% end %>
#
# Options:
# [+name+]
# The name of the block being defined (either a string or a symbol)
# [+options+]
# The default options for the block definition. Any or all of these options may be overrideen by
# whomever calls "blocks.render" on this block.
# [+block+]
# The block that is to be rendered when "blocks.render" is called for this block.
def define(name, options={}, &block)
collection = options.delete(:collection)
if collection
collection.each do |object|
define(view.call_if_proc(name, object, options), options, &block)
end
else
self.define_block_container(name, options, &block)
end
nil
end
# Define a block, replacing an existing block by the same name if it is already defined.
# <%= blocks.define :some_block_name, :parameter1 => "1", :parameter2 => "2" do |options| %>
# <%= options[:parameter1] %> and <%= options[:parameter2] %>
# <% end %>
#
# <%= blocks.replace :some_block_name, :parameter3 => "3", :parameter4 => "4" do |options| %>
# <%= options[:parameter3] %> and <%= options[:parameter4] %>
# <% end %>
# Options:
# [+name+]
# The name of the block being defined (either a string or a symbol)
# [+options+]
# The default options for the block definition. Any or all of these options may be overrideen by
# whomever calls "blocks.render" on this block.
# [+block+]
# The block that is to be rendered when "blocks.render" is called for this block.
def replace(name, options={}, &block)
blocks[name.to_sym] = nil
self.define_block_container(name, options, &block)
nil
end
# Render a block, first rendering any "before" blocks, then rendering the block itself, then rendering
# any "after" blocks. Additionally, a collection may also be passed in, and Blocks will render
# an the block, along with corresponding before and after blocks for each element of the collection.
# Blocks will make either two or four different attempts to render the block, depending on how use_partials
# is globally set, or an option is passed in to the render call to either use partials or skip partials:
# 1) Look for a block that has been defined inline elsewhere, using the blocks.define method:
# <% blocks.define :wizard do |options| %>
# Inline Block Step#<%= options[:step] %>.
# <% end %>
#
# <%= blocks.render :wizard, :step => @step %>
# 2) [IF use_partials is globally set to true or passed in as a runtime option,
# and skip_partials is not passed in as a runtime option]
# Look for a partial within the current controller's view directory:
# <%= blocks.render :wizard, :step => @step %>
#
#
# Controller-specific Block Step# <%= step %>.
# 3) [IF use_partials is globally set to true or passed in as a runtime option,
# and skip_partials is not passed in as a runtime option]
# Look for a partial with the global blocks view directory (by default /app/views/blocks/):
# <%= blocks.render :wizard, :step => @step %>
#
#
# Global Block Step#<%= step %>.
# 4) Render the default implementation for the block if provided to the blocks.render call:
# <%= blocks.render :wizard, :step => @step do |options| do %>
# Default Implementation Block Step#<%= options %>.
# <% end %>
# Options:
# [+name_or_container+]
# The name of the block to render (either a string or a symbol)
# [+*args+]
# Any arguments to pass to the block to be rendered (and also to be passed to any "before" and "after" blocks).
# The last argument in the list can be a hash and can include the following special options:
# [:collection]
# The collection of elements to render blocks for
# [:as]
# The variable name to assign the current element in the collection being rendered over
# [:surrounding_tag]
# The content tag to render around a block, which might be particularly useful when rendering a collection of blocks,
# such as for a list or table
# [:surrounding_tag_html]
# The attributes to be applied to the HTML content tag, such as styling or special properties. Please note, any Procs passed
# in will automatically be evaluated (For example: :class => lambda { cycle("even", "odd") })
# [:use_partials]
# Overrides the globally defined use_partials and tells Blocks to render partials in trying to render a block
# [:skip_partials]
# Overrides the globally defined use_partials and tells Blocks to not render any partials in trying to render a block
# [+block+]
# The default block to render if no such block block that is to be rendered when "blocks.render" is called for this block.
def render(name_or_container, *args, &block)
options = args.extract_options!
collection = options.delete(:collection)
buffer = ActiveSupport::SafeBuffer.new
if collection
as = options.delete(:as)
collection.each do |object|
cloned_args = args.clone
cloned_args.unshift(object)
cloned_options = options.clone
cloned_options = cloned_options.merge(object.options) if object.is_a?(Blocks::Container)
cloned_args.push(cloned_options)
block_name = view.call_if_proc(name_or_container, *cloned_args)
as_name = (as.presence || block_name).to_sym
cloned_options[as_name] = object
buffer << render(block_name, *cloned_args, &block)
end
else
surrounding_tag = options.delete(:surrounding_tag)
surrounding_tag_html = options.delete(:surrounding_tag_html)
args.push(options)
if surrounding_tag_surrounds_before_and_after_blocks
buffer << content_tag(surrounding_tag, surrounding_tag_html, *args) do
temp_buffer = ActiveSupport::SafeBuffer.new
temp_buffer << render_before_blocks(name_or_container, *args)
temp_buffer << render_block_with_around_blocks(name_or_container, *args, &block)
temp_buffer << render_after_blocks(name_or_container, *args)
end
else
buffer << render_before_blocks(name_or_container, *args)
buffer << content_tag(surrounding_tag, surrounding_tag_html, *args) do
render_block_with_around_blocks(name_or_container, *args, &block)
end
buffer << render_after_blocks(name_or_container, *args)
end
end
buffer
end
alias use render
# Render a block, first rendering any "before" blocks, then rendering the block itself, then rendering
# any "after" blocks. Additionally, a collection may also be passed in, and Blocks will render
# an the block, along with corresponding before and after blocks for each element of the collection.
# Blocks will make two different attempts to render block:
# 1) Look for a block that has been defined inline elsewhere, using the blocks.define method:
# <% blocks.define :wizard do |options| %>
# Inline Block Step#<%= options[:step] %>.
# <% end %>
#
# <%= blocks.render :wizard, :step => @step %>
# 2) Render the default implementation for the block if provided to the blocks.render call:
# <%= blocks.render :wizard, :step => @step do |options| do %>
# Default Implementation Block Step#<%= options %>.
# <% end %>
# Options:
# [+name_or_container+]
# The name of the block to render (either a string or a symbol)
# [+*args+]
# Any arguments to pass to the block to be rendered (and also to be passed to any "before" and "after" blocks).
# The last argument in the list can be a hash and can include the following special options:
# [:collection]
# The collection of elements to render blocks for
# [:as]
# The variable name to assign the current element in the collection being rendered over
# [:surrounding_tag]
# The content tag to render around a block, which might be particularly useful when rendering a collection of blocks,
# such as for a list or table
# [:surrounding_tag_html]
# The attributes to be applied to the HTML content tag, such as styling or special properties. Please note, any Procs passed
# in will automatically be evaluated (For example: :class => lambda { cycle("even", "odd") })
# [+block+]
# The default block to render if no such block block that is to be rendered when "blocks.render" is called for this block.
def render_without_partials(name_or_container, *args, &block)
options = args.extract_options!
options[:skip_partials] = true
args.push(options)
render(name_or_container, *args, &block)
end
# Render a block, first rendering any "before" blocks, then rendering the block itself, then rendering
# any "after" blocks. Additionally, a collection may also be passed in, and Blocks will render
# an the block, along with corresponding before and after blocks for each element of the collection.
# Blocks will make four different attempts to render block:
# 1) Look for a block that has been defined inline elsewhere, using the blocks.define method:
# <% blocks.define :wizard do |options| %>
# Inline Block Step#<%= options[:step] %>.
# <% end %>
#
# <%= blocks.render :wizard, :step => @step %>
# 2) Look for a partial within the current controller's view directory:
# <%= blocks.render :wizard, :step => @step %>
#
#
# Controller-specific Block Step# <%= step %>.
# 3) Look for a partial with the global blocks view directory (by default /app/views/blocks/):
# <%= blocks.render :wizard, :step => @step %>
#
#
# Global Block Step#<%= step %>.
# 4) Render the default implementation for the block if provided to the blocks.render call:
# <%= blocks.render :wizard, :step => @step do |options| do %>
# Default Implementation Block Step#<%= options %>.
# <% end %>
# Options:
# [+name_or_container+]
# The name of the block to render (either a string or a symbol)
# [+*args+]
# Any arguments to pass to the block to be rendered (and also to be passed to any "before" and "after" blocks).
# The last argument in the list can be a hash and can include the following special options:
# [:collection]
# The collection of elements to render blocks for
# [:as]
# The variable name to assign the current element in the collection being rendered over
# [:surrounding_tag]
# The content tag to render around a block, which might be particularly useful when rendering a collection of blocks,
# such as for a list or table
# [:surrounding_tag_html]
# The attributes to be applied to the HTML content tag, such as styling or special properties. Please note, any Procs passed
# in will automatically be evaluated (For example: :class => lambda { cycle("even", "odd") })
# [+block+]
# The default block to render if no such block block that is to be rendered when "blocks.render" is called for this block.
def render_with_partials(name_or_container, *args, &block)
options = args.extract_options!
options[:use_partials] = true
args.push(options)
render(name_or_container, *args, &block)
end
# Queue a block for later rendering, such as within a template.
# <%= Blocks::Base.new(self).render_template("shared/wizard") do |blocks| %>
# <% blocks.queue :step1 %>
# <% blocks.queue :step2 do %>
# My overridden Step 2 |
# <% end %>
# <% blocks.queue :step3 %>
# <% blocks.queue do %>
# | Anonymous Step 4
# <% end %>
# <% end %>
#
#
# <% blocks.define :step1 do %>
# Step 1 |
# <% end %>
#
# <% blocks.define :step2 do %>
# Step 2 |
# <% end %>
#
# <% blocks.define :step3 do %>
# Step 3
# <% end %>
#
# <% blocks.queued_blocks.each do |block| %>
# <%= blocks.render block %>
# <% end %>
#
#
# Options:
# [+*args+]
# The options to pass in when this block is rendered. These will override any options provided to the actual block
# definition. Any or all of these options may be overriden by whoever calls "blocks.render" on this block.
# Usually the first of these args will be the name of the block being queued (either a string or a symbol)
# [+block+]
# The optional block definition to render when the queued block is rendered
def queue(*args, &block)
self.queued_blocks << self.define_block_container(*args, &block)
nil
end
# Render a partial, treating it as a template, and any code in the block argument will impact how the template renders
# <%= Blocks::Base.new(self).render_template("shared/wizard") do |blocks| %>
# <% blocks.queue :step1 %>
# <% blocks.queue :step2 do %>
# My overridden Step 2 |
# <% end %>
# <% blocks.queue :step3 %>
# <% blocks.queue do %>
# | Anonymous Step 4
# <% end %>
# <% end %>
#
#
# <% blocks.define :step1 do %>
# Step 1 |
# <% end %>
#
# <% blocks.define :step2 do %>
# Step 2 |
# <% end %>
#
# <% blocks.define :step3 do %>
# Step 3
# <% end %>
#
# <% blocks.queued_blocks.each do |block| %>
# <%= blocks.render block %>
# <% end %>
#
#
# Options:
# [+partial+]
# The partial to render as a template
# [+block+]
# An optional block with code that affects how the template renders
def render_template(partial, &block)
render_options = global_options.clone
render_options[self.variable] = self
render_options[:captured_block] = view.capture(self, &block) if block_given?
view.render partial, render_options
end
# Add a block to render before another block. This before block will be put into an array so that multiple
# before blocks may be queued. They will render in the order in which they are declared when the
# "blocks#render" method is called. Any options specified to the before block will override any options
# specified in the block definition.
# <% blocks.define :wizard, :option1 => 1, :option2 => 2 do |options| %>
# Step 2 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)
# <% end %>
#
# <% blocks.before :wizard, :option1 => 3 do
# Step 0 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)
# <% end %>
#
# <% blocks.before :wizard, :option2 => 4 do
# Step 1 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)
# <% end %>
#
# <%= blocks.render :wizard %>
#
#
#
# <%= blocks.render :wizard, :step => @step %>
# Options:
# [+name+]
# The name of the block to render this code before when that block is rendered
# [+options+]
# Any options to specify to the before block when it renders. These will override any options
# specified when the block was defined.
# [+block+]
# The block of code to render before another block
def before(name, options={}, &block)
self.queue_block_container("before_#{name.to_s}", options, &block)
nil
end
alias prepend before
# Add a block to render after another block. This after block will be put into an array so that multiple
# after blocks may be queued. They will render in the order in which they are declared when the
# "blocks#render" method is called. Any options specified to the after block will override any options
# specified in the block definition.
# <% blocks.define :wizard, :option1 => 1, :option2 => 2 do |options| %>
# Step 2 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)
# <% end %>
#
# <% blocks.after :wizard, :option1 => 3 do
# Step 3 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)
# <% end %>
#
# <% blocks.after :wizard, :option2 => 4 do
# Step 4 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)
# <% end %>
#
# <%= blocks.render :wizard %>
#
#
#
# <%= blocks.render :wizard, :step => @step %>
# Options:
# [+name+]
# The name of the block to render this code after when that block is rendered
# [+options+]
# Any options to specify to the after block when it renders. These will override any options
# specified when the block was defined.
# [+block+]
# The block of code to render after another block
def after(name, options={}, &block)
self.queue_block_container("after_#{name.to_s}", options, &block)
nil
end
alias append after
alias for after
# Add a block to render around another block. This around block will be put into an array so that multiple
# around blocks may be queued. They will render in the order in which they are declared when the
# "blocks#render" method is called, with the last declared around block being rendered as the outer-most code, and
# the first declared around block rendered as the inner-most code. Any options specified to the after block will override any options
# specified in the block definition. The user of an around block must declare a block with at least one parameter and
# should invoke the #call method on that argument.
#
# <% blocks.define :my_block do %>
# test
# <% end %>
#
# <% blocks.around :my_block do |content_block| %>
#