require 'active_support'
module Blocks
class Base
include CallWithParams
# a pointer to the ActionView that called Blocks
attr_accessor :view
# Hash of block names to Blocks::Container objects
attr_accessor :blocks
# counter, used to give unnamed blocks a unique name
attr_accessor :anonymous_block_number
# These are the options that are passed into the initalize method
attr_accessor :global_options
# Hash of block names that have been explicitely skipped
attr_accessor :skipped_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].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 or a Proc)
# [+options+]
# The default options for the block definition. Any or all of these options may be overridden by
# whomever calls "blocks.render" on this block. If :collection => some_array,
# Blocks will assume that the first argument is a Proc and define a block for each object in the
# collection
# [+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(call_with_params(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 overridden 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] = nil
self.define_block_container(name, options, &block)
nil
end
# Skip the rendering of a particular block when blocks.render is called for the a particular block name
# <%= blocks.define :some_block_name do %>
# My output
# <% end %>
#
# <%= blocks.skip :some_block_name %>
#
# <%= blocks.render :some_block_name %>
# <%# will not render anything %>
# Options:
# [+name+]
# The name of the block to skip rendering for
def skip(name)
blocks[name] = nil
skipped_blocks[name] = true
self.define_block_container(name) do
end
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
# [:wrap_with]
# The content tag to render around this block (For example: :wrap_with => {:tag => TAG_TYPE, :class => "my-class", :style => "border: 1px solid black"})
# [:wrap_each]
# The content tag to render around each item in a collection (For example: :wrap_each { :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)
name = extract_block_name name_or_container
if skipped_blocks[name] && global_options.skip_applies_to_surrounding_blocks
return
end
buffer = ActiveSupport::SafeBuffer.new
wrap_with = options.delete(:wrap_with) || {}
if collection
as = options.delete(:as)
wrap_each = options.delete(:wrap_each) || {}
buffer = content_tag_with_block(wrap_with[:tag], wrap_with.except(:tag), *args) do
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 = call_with_params(name_or_container, *cloned_args)
as_name = (as.presence || block_name).to_sym
cloned_options[as_name] = object
cloned_options[:wrap_with] = wrap_each
buffer << render(block_name, *cloned_args, &block)
end
buffer
end
else
args.push(options)
if global_options.merge(options)[:wrap_before_and_after_blocks]
buffer << content_tag_with_block(wrap_with[:tag], wrap_with.except(:tag), *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_with_block(wrap_with[:tag], wrap_with.except(:tag), *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
# [:wrap_with]
# The content tag to render around this block (For example: :wrap_with => {:tag => TAG_TYPE, :class => "my-class", :style => "border: 1px solid black"})
# [:wrap_each]
# The content tag to render around each item in a collection (For example: :wrap_each { :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
# [:wrap_with]
# The content tag to render around this block (For example: :wrap_with => {:tag => TAG_TYPE, :class => "my-class", :style => "border: 1px solid black"})
# [:wrap_each]
# The content tag to render around each item in a collection (For example: :wrap_each { :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
# 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.add_block_container_to_list("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.add_block_container_to_list("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| %>
#