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| %> #

# <%= content_block.call %> #

# <% end %> # # <% blocks.around :my_block do |content_block| %> # # <%= content_block.call %> # # <% end %> # # <%= blocks.render :my_block %> # #