blocks 1.2.5 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ 2.1.0 (July 3, 2013)
2
+ * Biggest change is that this gem was moved back to the blocks repo and renamed from building-blocks to blocks, it's original name.
3
+ * changed rendering so that partials are no longer rendered by default
4
+ * surrounding_tag_surrounds_before_and_after_blocks is now false, by default, meaning that if a block has a surrounding_tag, it will only render the block's contents inside that tag, and any before and after blocks will render outside that tag.
5
+ * allows render options to be skipped or provider new helper methods, building blocks from the controller, got rid of aliases for "blocks", specify render order?
6
+
7
+ 2.0.0 (October 18, 2012)
8
+
9
+ * Added ability to render a collection of objects and specify the surrounding elements and the html to apply to those surrounding elements
10
+ For example: blocks.render :test, :collection => @cuisine_types, :as => :cuisine_type, :surrounding_tag => "div", :surrounding_tag_html => {:class => Proc.new {cycle("even", "odd")}}
11
+ * Added ability to specify "around" blocks, i.e. blocks of view code that render around another block of code, such as:
12
+ <% blocks.around :test_block do |content_block| %>
13
+ <h1>
14
+ <%= content_block.call %>
15
+ </h1>
16
+ <% end %> %>
17
+ * Added a util method BuildingBlocks.render_template(self, partial, options={}, &block) that makes the templating feature easier to use
18
+ (as opposed to calling the long way: BuildingBlocks::Base.new(self, options={}).render_template(partial, &block))
19
+ * Aliased "#blocks" method that is available to views as "#buildingblocks", "#bb", and "#building_blocks"
20
+ * Aliased "#after" method to "#for", so that you can now call <%= blocks.for :block_name %> (should be slightly more familiar to users of content_for with yield)
21
+ * Added a "#setup" method to initialize BuildingBlocks globally (for example: BuildingBlocks.setup do |config| config.template_folder = "shared" end)
22
+ * Cleaned up the organization of the code base and now using autoload instead of require
23
+ * Removed :use_partials_for_before_and_after_hooks option from 1.2.2
24
+
25
+ 1.2.3 (February 9, 2012)
26
+
27
+ * Created two new utility methods: evaluated_procs and evaluated_proc that allow parameters for blocks to be Proc objects so long as these methods are called to evaluate them. (These methods have been carried over and renamed from the table-for gem where they were used to be able to dynamically specify table css classes, styles, and ids at runtime).
28
+
29
+ 1.2.2 (February 9, 2012)
30
+
31
+ * Allow :use_partials and :use_partials_for_before_and_after_hooks to be passed in as initialization options to BuildingBlocks::Base to control whether BuildingBlocks attempts to render partials when "blocks.render" is called.
32
+
33
+ 1.2.1 (February 7, 2012)
34
+
35
+ * Only try to render "before" and "after" blocks as partials if that BuildingBlocks::USE_PARTIALS_FOR_BEFORE_AND_AFTER_HOOKS is globally set to true (set to false now by default)
36
+
37
+ 1.2.0 (February 5, 2012)
38
+
39
+ * Changed prototype for "use" method, such that the name of the block being rendered is now a required parameter.
40
+ * Documented BuildingBlocks::Base more thoroughly
41
+ * Changed the blocks.use method to blocks.render (blocks.use is still available for legacy purposes)
42
+ * Removed the original render method and replaced with render_template that takes the partial and block to render as arguments.
43
+
44
+ 1.1.0 (February 4, 2012)
45
+
46
+ * Ability to disable use of partials when rendering a block
47
+ * Ability to disable use of partials for before and after hooks
48
+ * Complete test coverage
49
+ * :template_folder and :variable options are no longer being passed in as part of the options hash to defined blocks and partials
@@ -1,141 +1 @@
1
- = Blocks
2
-
3
- Wiki[http://wiki.github.com/hunterae/blocks]
4
-
5
- Blocks is a replacement / complement to content_for with yield. It allows a user to specify a block capable of taking parameters that may be passed in when using that block. A user may also specify other blocks to be prepended or appended before or after a specific block is rendered. A template may also be specified to blocks that will provide the layout for a specific component and provide default implementations for its blocks. In this way, blocks is able to offer a very simple to use table generator ({table_for}[https://github.com/hunterae/blocks/wiki/table_for]) and list generator (list_for).
6
-
7
- == Installation
8
-
9
- In <b>Rails 3</b>, add this to your Gemfile.
10
-
11
- gem "blocks"
12
-
13
- In <b>Rails 2</b>, add this to your environment.rb file. (At this time, it is untested in Rails 2)
14
-
15
- config.gem "blocks"
16
-
17
- Alternatively, you can install it as a plugin.
18
-
19
- rails plugin install git://github.com/hunterae/blocks.git
20
-
21
-
22
- == Getting Started
23
-
24
- What makes blocks particularly powerful is that it represent a combination of the ruby definition of blocks and partials. A block may be defined inline utilizing syntax similar to that of content_for:
25
-
26
- <%= blocks.define :some_block_name, :some_parameter => 1, :some_parameter2 => 2 do |options| %>
27
- <%= options[:some_parameter] %>
28
- <%= options[:some_parameter2] %>
29
- <%= options[:some_parameter3] %>
30
- <% end %>
31
-
32
- or it may be written as a partial, existing in the controller view directory for whatever controller renders the view, or the globally configured blocks directory (views/blocks by default). The above block definition might then be written in the file views/blocks/_some_block_name.html.erb:
33
-
34
- <%= some_parameter if defined?(some_parameter)%>
35
- <%= some_parameter2 if defined?(some_parameter2) %>
36
- <%= some_parameter3 if defined?(some_parameter3) %>
37
-
38
- Notice that in the context of a partial, we have direct access to the parameters as local variables. However, since we're not utilizing "blocks.define" to create our block, we can't specify the default parameters :some_parameter and :some_parameter2 as we do in the first example. This has the potential to cause a runtime exception in the context of partials and we will need to assure that optional parameters are defined using "defined?".
39
-
40
- In the first example, we specified two parameters that are provided automatically to the block named "some_block_name". They are "some_parameter" and "some_parameter2". In addition, the definition of the block is assuming that the user will be passing in another parameter called "some_parameter3". However, all parameters are passed in in a hash (or local variables for partials), no error will occur if the user fails to specify "some_parameter3".
41
-
42
- Now, we may use the above block as follows:
43
-
44
- <%= blocks.use :some_block_name %>
45
-
46
- The system will first look for a block that's been defined inline by the name :some_block_name. If it cannot find one, it will try and render a partial with the same name within the current controller's view directory. Failing to find that, it will try and render a partial within the global blocks view directory (by default, views/blocks). If that partial also does not exist, it will give up and render nothing. (See [[Render Order]])
47
-
48
- Here, we will see the output "1 2" for the first example (and "" for the second example). But if we pass in "some_parameter3", as follows:
49
-
50
- <%= blocks.use :some_block_name, :some_parameter3 => 3 %>
51
-
52
- Then we will see "1 2 3" for the first example (and "3" for the second example). Additionally, we can override any of the previous parameters when using a block simply by passing in the new value as a parameter:
53
-
54
- <%= blocks.use :some_block_name, :some_parameter2 => "overridden", :some_parameter3 => 3 %>
55
-
56
- In this case, we will see "1 overridden 3" for the first example (and "overridden 3" for the second example). Thus, we now have content_for but with parameters, and additional intelligence by utilizing partials.
57
-
58
- == table_for
59
-
60
- table_for is a useful example of how blocks may be utilized in an invaluable way. It is built entirely using the core blocks library. It is exposed as a helper method with the following prototype:
61
-
62
- table_for(records, options={}, &block)
63
-
64
- For example, if you have an Array of objects that respond to the following [:name, :email, :phonenumber] e.g.
65
-
66
- @records = [OpenStruct.new({:name => "The Name", :email => "The Email", :phonenumber => "A phone number"}),
67
- OpenStruct.new({:name => "The Second Name", :email => "The Second Email", :phonenumber => "A second phone number"})]
68
-
69
- And using table_for you can very easily specify intricate details about the table (Note: most of the options in this example are optional):
70
-
71
- <%= table_for @records,
72
- :table_html => {:id => "records"},
73
- :header_html => {:style => "color:red"},
74
- :row_html => {:class => lambda { |parameters| cycle('odd', 'even')}},
75
- :column_html => {:style => "color:green"} do |table| %>
76
- <%= table.column :name, :column_html => {:style => "color:blue"}, :header_html => {:style => "color:orange"} %>
77
- <%= table.column :email %>
78
- <%= table.column :phonenumber, :column_html => {:style => "color:orange"}, :header_html => {:style => "color:blue"} %>
79
- <%= table.column :label => "???" do %>
80
- Some Random Column
81
- <% end %>
82
- <% end %>
83
-
84
- Will generate the following table:
85
-
86
- <table id="records">
87
- <thead>
88
- <tr>
89
- <th style="color:orange">Name</th>
90
- <th style="color:red">Email</th>
91
- <th style="color:blue">Phonenumber</th>
92
- <th style="color:red">???</th>
93
- </tr>
94
- </thead>
95
- <tbody>
96
- <tr class="odd">
97
- <td style="color:blue">The Name</td>
98
- <td style="color:green">The Email</td>
99
- <td style="color:orange">A phone number</td>
100
- <td style="color:green">Some Random Column</td>
101
- </tr>
102
- <tr class="even">
103
- <td style="color:blue">The Second Name</td>
104
- <td style="color:green">The Second Email</td>
105
- <td style="color:orange">A second phone number</td>
106
- <td style="color:green">Some Random Column</td>
107
- </tr>
108
- </tbody>
109
- </table>
110
-
111
- See {table_for}[https://github.com/hunterae/blocks/wiki/table_for] for details.
112
-
113
- == Building Layouts
114
-
115
- Utilizing blocks, we can rethink the way we define our templates. Everywhere where we might be tempted to write "yield :some_block_name", we can replace with "blocks.use :some_block_name". This gives us
116
- the ability to provide a default implementation for that block if it cannot be found (see {Providing a Default Implementation of a Block}[https://github.com/hunterae/blocks/wiki/Providing-a-Default-Implementation-of-a-Block]), and provides hooks for being able to render code before and after :some_block_name, utilizing "blocks.before :some_block_name" and "blocks.after :some_block_name" (See {Before and After Blocks}[https://github.com/hunterae/blocks/wiki/Before-and-After-Blocks]]).
117
-
118
- See {Building Layouts}[https://github.com/hunterae/blocks/wiki/Building-Layouts] for more details on how to utilize Blocks to build layouts.
119
-
120
- == Wiki Docs
121
-
122
- * {Basics}[https://github.com/hunterae/blocks/wiki/Basics]
123
- * {Specifying your blocks as controller level partials}[https://github.com/hunterae/blocks/wiki/Specifying-your-blocks-as-controller-level-partials]
124
- * {Specifying your blocks as global partials}[https://github.com/hunterae/blocks/wiki/Specifying-your-blocks-as-global-partials]
125
- * {How Inline Blocks, Controller Level Blocks, and Global Blocks Work Together}[https://github.com/hunterae/blocks/wiki/How-Inline-Blocks,-Controller-Level-Blocks,-and-Global-Blocks-Work-Together]
126
- * {Building Layouts}[https://github.com/hunterae/blocks/wiki/Building-Layouts]
127
- * {Render Order}[https://github.com/hunterae/blocks/wiki/Render-Order]
128
- * {Before and After Blocks}[https://github.com/hunterae/blocks/wiki/Before-and-After-Blocks]
129
- * {Providing a Default Implementation of a Block}[https://github.com/hunterae/blocks/wiki/Providing-a-Default-Implementation-of-a-Block]
130
- * {table_for}[https://github.com/hunterae/blocks/wiki/table_for]
131
- * {Overriding the defaults in table_for}[https://github.com/hunterae/blocks/wiki/Overriding-the-defaults-in-table_for]
132
- * {Defining your own reusage table_for implementation}[https://github.com/hunterae/blocks/wiki/Defining-your-own-reusage-table_for-implementation]
133
- * {See more}[https://github.com/hunterae/blocks/wiki]
134
-
135
- == Questions or Problems?
136
-
137
- If you have any issues with Blocks which you cannot find the solution to in the documentation[https://github.com/hunterae/blocks/wiki], please add an {issue on GitHub}[https://github.com/hunterae/blocks/issues] or fork the project and send a pull request.
138
-
139
- == Special Thanks
140
-
141
- Thanks to Todd Fisher of Captico for implementation help and setup of gem and to Jon Phillips for suggestions and use case help.
1
+ Note: I am going to be completely rewriting this documentation, and what was here may have been invalid so stay tuned.
@@ -0,0 +1,22 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new('spec')
4
+
5
+ # If you want to make this the default task
6
+ task :default => :spec
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |gemspec|
11
+ gemspec.name = "blocks"
12
+ gemspec.summary = "Blocks is an intricate way of rendering blocks of code, while combining some of the best features of content blocks and partials, and adding several new features that go above and beyond what a simple content_for with yield or a render :partial is capable of doing."
13
+ gemspec.description = "Blocks goes beyond blocks and partials"
14
+ gemspec.email = "hunterae@gmail.com"
15
+ gemspec.homepage = "http://github.com/hunterae/blocks"
16
+ gemspec.authors = ["Andrew Hunter"]
17
+ gemspec.files = FileList["[A-Z]*", "{lib,spec,rails}/**/*"] - FileList["**/*.log", "Gemfile", "Gemfile.lock"]
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler not available. Install it with: gem install jeweler"
22
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.1.0
@@ -1,5 +1,31 @@
1
- require 'blocks/container'
2
- require 'blocks/builder'
3
- require 'blocks/table_for'
4
- require 'blocks/list_for'
5
- require 'blocks/engine'
1
+ require "action_view"
2
+ require "action_controller"
3
+
4
+ module Blocks
5
+ autoload :Base, "blocks/base"
6
+ autoload :Container, "blocks/container"
7
+ autoload :ViewAdditions, "blocks/view_additions"
8
+ autoload :ControllerAdditions, "blocks/controller_additions"
9
+
10
+ mattr_accessor :template_folder
11
+ @@template_folder = "blocks"
12
+
13
+ mattr_accessor :use_partials
14
+ @@use_partials = false
15
+
16
+ mattr_accessor :surrounding_tag_surrounds_before_and_after_blocks
17
+ @@surrounding_tag_surrounds_before_and_after_blocks = false
18
+
19
+ # Shortcut for using the templating feature / rendering templates
20
+ def self.render_template(view, partial, options={}, &block)
21
+ Blocks::Base.new(view, options).render_template(partial, &block)
22
+ end
23
+
24
+ # Default way to setup Blocks
25
+ def self.setup
26
+ yield self
27
+ end
28
+ end
29
+
30
+ ActionView::Base.send :include, Blocks::ViewAdditions::ClassMethods
31
+ ActionController::Base.send :include, Blocks::ControllerAdditions::ClassMethods
@@ -0,0 +1,672 @@
1
+ module Blocks
2
+ class Base
3
+ # a pointer to the ActionView that called Blocks
4
+ attr_accessor :view
5
+
6
+ # Hash of block names to Blocks::Container objects
7
+ attr_accessor :blocks
8
+
9
+ # Array of Blocks::Container objects, storing the order of blocks as they were queued
10
+ attr_accessor :queued_blocks
11
+
12
+ # counter, used to give unnamed blocks a unique name
13
+ attr_accessor :anonymous_block_number
14
+
15
+ # A Hash of queued_blocks arrays; a new array is started when method_missing is invoked
16
+ attr_accessor :block_groups
17
+
18
+ # These are the options that are passed into the initalize method
19
+ attr_accessor :global_options
20
+
21
+ # The default folder to look in for global partials
22
+ attr_accessor :template_folder
23
+
24
+ # The variable to use when rendering the partial for the templating feature (by default, "blocks")
25
+ attr_accessor :variable
26
+
27
+ # Boolean variable for whether Blocks should attempt to render blocks as partials if a defined block cannot be found
28
+ attr_accessor :use_partials
29
+
30
+ # Boolean variable for whether Blocks should render before and after blocks inside or outside of a collections' elements' surrounding tags
31
+ attr_accessor :surrounding_tag_surrounds_before_and_after_blocks
32
+
33
+ # Checks if a particular block has been defined within the current block scope.
34
+ # <%= blocks.defined? :some_block_name %>
35
+ # Options:
36
+ # [+name+]
37
+ # The name of the block to check
38
+ def defined?(name)
39
+ !blocks[name.to_sym].nil?
40
+ end
41
+
42
+ # Define a block, unless a block by the same name is already defined.
43
+ # <%= blocks.define :some_block_name, :parameter1 => "1", :parameter2 => "2" do |options| %>
44
+ # <%= options[:parameter1] %> and <%= options[:parameter2] %>
45
+ # <% end %>
46
+ #
47
+ # Options:
48
+ # [+name+]
49
+ # The name of the block being defined (either a string or a symbol)
50
+ # [+options+]
51
+ # The default options for the block definition. Any or all of these options may be overrideen by
52
+ # whomever calls "blocks.render" on this block.
53
+ # [+block+]
54
+ # The block that is to be rendered when "blocks.render" is called for this block.
55
+ def define(name, options={}, &block)
56
+ collection = options.delete(:collection)
57
+
58
+ if collection
59
+ collection.each do |object|
60
+ define(evaluated_proc(name, object, options), options, &block)
61
+ end
62
+ else
63
+ self.define_block_container(name, options, &block)
64
+ end
65
+
66
+ nil
67
+ end
68
+
69
+ # Define a block, replacing an existing block by the same name if it is already defined.
70
+ # <%= blocks.define :some_block_name, :parameter1 => "1", :parameter2 => "2" do |options| %>
71
+ # <%= options[:parameter1] %> and <%= options[:parameter2] %>
72
+ # <% end %>
73
+ #
74
+ # <%= blocks.replace :some_block_name, :parameter3 => "3", :parameter4 => "4" do |options| %>
75
+ # <%= options[:parameter3] %> and <%= options[:parameter4] %>
76
+ # <% end %>
77
+ # Options:
78
+ # [+name+]
79
+ # The name of the block being defined (either a string or a symbol)
80
+ # [+options+]
81
+ # The default options for the block definition. Any or all of these options may be overrideen by
82
+ # whomever calls "blocks.render" on this block.
83
+ # [+block+]
84
+ # The block that is to be rendered when "blocks.render" is called for this block.
85
+ def replace(name, options={}, &block)
86
+ blocks[name.to_sym] = nil
87
+ self.define_block_container(name, options, &block)
88
+ nil
89
+ end
90
+
91
+ # Render a block, first rendering any "before" blocks, then rendering the block itself, then rendering
92
+ # any "after" blocks. Additionally, a collection may also be passed in, and Blocks will render
93
+ # an the block, along with corresponding before and after blocks for each element of the collection.
94
+ # Blocks will make either two or four different attempts to render the block, depending on how use_partials
95
+ # is globally set, or an option is passed in to the render call to either use partials or skip partials:
96
+ # 1) Look for a block that has been defined inline elsewhere, using the blocks.define method:
97
+ # <% blocks.define :wizard do |options| %>
98
+ # Inline Block Step#<%= options[:step] %>.
99
+ # <% end %>
100
+ #
101
+ # <%= blocks.render :wizard, :step => @step %>
102
+ # 2) [IF use_partials is globally set to true or passed in as a runtime option,
103
+ # and skip_partials is not passed in as a runtime option]
104
+ # Look for a partial within the current controller's view directory:
105
+ # <%= blocks.render :wizard, :step => @step %>
106
+ #
107
+ # <!-- In /app/views/pages/_wizard.html.erb (assuming it is the pages controller running): -->
108
+ # Controller-specific Block Step# <%= step %>.
109
+ # 3) [IF use_partials is globally set to true or passed in as a runtime option,
110
+ # and skip_partials is not passed in as a runtime option]
111
+ # Look for a partial with the global blocks view directory (by default /app/views/blocks/):
112
+ # <%= blocks.render :wizard, :step => @step %>
113
+ #
114
+ # <!-- In /app/views/blocks/_wizard.html.erb: -->
115
+ # Global Block Step#<%= step %>.
116
+ # 4) Render the default implementation for the block if provided to the blocks.render call:
117
+ # <%= blocks.render :wizard, :step => @step do |options| do %>
118
+ # Default Implementation Block Step#<%= options %>.
119
+ # <% end %>
120
+ # Options:
121
+ # [+name_or_container+]
122
+ # The name of the block to render (either a string or a symbol)
123
+ # [+*args+]
124
+ # Any arguments to pass to the block to be rendered (and also to be passed to any "before" and "after" blocks).
125
+ # The last argument in the list can be a hash and can include the following special options:
126
+ # [:collection]
127
+ # The collection of elements to render blocks for
128
+ # [:as]
129
+ # The variable name to assign the current element in the collection being rendered over
130
+ # [:surrounding_tag]
131
+ # The content tag to render around a block, which might be particularly useful when rendering a collection of blocks,
132
+ # such as for a list or table
133
+ # [:surrounding_tag_html]
134
+ # The attributes to be applied to the HTML content tag, such as styling or special properties. Please note, any Procs passed
135
+ # in will automatically be evaluated (For example: :class => lambda { cycle("even", "odd") })
136
+ # [:use_partials]
137
+ # Overrides the globally defined use_partials and tells Blocks to render partials in trying to render a block
138
+ # [:skip_partials]
139
+ # Overrides the globally defined use_partials and tells Blocks to not render any partials in trying to render a block
140
+ # [+block+]
141
+ # The default block to render if no such block block that is to be rendered when "blocks.render" is called for this block.
142
+ def render(name_or_container, *args, &block)
143
+ options = args.extract_options!
144
+ collection = options.delete(:collection)
145
+
146
+ buffer = ActiveSupport::SafeBuffer.new
147
+
148
+ if collection
149
+ as = options.delete(:as)
150
+
151
+ collection.each do |object|
152
+ cloned_args = args.clone
153
+ cloned_args.unshift(object)
154
+ cloned_options = options.clone
155
+ cloned_options = cloned_options.merge(object.options) if object.is_a?(Blocks::Container)
156
+ cloned_args.push(cloned_options)
157
+
158
+ block_name = evaluated_proc(name_or_container, *cloned_args)
159
+ as_name = (as.presence || block_name).to_sym
160
+ cloned_options[as_name] = object
161
+
162
+ buffer << render(block_name, *cloned_args, &block)
163
+ end
164
+ else
165
+ surrounding_tag = options.delete(:surrounding_tag)
166
+ surrounding_tag_html = options.delete(:surrounding_tag_html)
167
+
168
+ args.push(options)
169
+
170
+ if surrounding_tag_surrounds_before_and_after_blocks
171
+ buffer << content_tag(surrounding_tag, surrounding_tag_html, *args) do
172
+ temp_buffer = ActiveSupport::SafeBuffer.new
173
+ temp_buffer << render_before_blocks(name_or_container, *args)
174
+ temp_buffer << render_block_with_around_blocks(name_or_container, *args, &block)
175
+ temp_buffer << render_after_blocks(name_or_container, *args)
176
+ end
177
+ else
178
+ buffer << render_before_blocks(name_or_container, *args)
179
+ buffer << content_tag(surrounding_tag, surrounding_tag_html, *args) do
180
+ render_block_with_around_blocks(name_or_container, *args, &block)
181
+ end
182
+ buffer << render_after_blocks(name_or_container, *args)
183
+ end
184
+ end
185
+
186
+ buffer
187
+ end
188
+ alias use render
189
+
190
+ # Render a block, first rendering any "before" blocks, then rendering the block itself, then rendering
191
+ # any "after" blocks. Additionally, a collection may also be passed in, and Blocks will render
192
+ # an the block, along with corresponding before and after blocks for each element of the collection.
193
+ # Blocks will make two different attempts to render block:
194
+ # 1) Look for a block that has been defined inline elsewhere, using the blocks.define method:
195
+ # <% blocks.define :wizard do |options| %>
196
+ # Inline Block Step#<%= options[:step] %>.
197
+ # <% end %>
198
+ #
199
+ # <%= blocks.render :wizard, :step => @step %>
200
+ # 2) Render the default implementation for the block if provided to the blocks.render call:
201
+ # <%= blocks.render :wizard, :step => @step do |options| do %>
202
+ # Default Implementation Block Step#<%= options %>.
203
+ # <% end %>
204
+ # Options:
205
+ # [+name_or_container+]
206
+ # The name of the block to render (either a string or a symbol)
207
+ # [+*args+]
208
+ # Any arguments to pass to the block to be rendered (and also to be passed to any "before" and "after" blocks).
209
+ # The last argument in the list can be a hash and can include the following special options:
210
+ # [:collection]
211
+ # The collection of elements to render blocks for
212
+ # [:as]
213
+ # The variable name to assign the current element in the collection being rendered over
214
+ # [:surrounding_tag]
215
+ # The content tag to render around a block, which might be particularly useful when rendering a collection of blocks,
216
+ # such as for a list or table
217
+ # [:surrounding_tag_html]
218
+ # The attributes to be applied to the HTML content tag, such as styling or special properties. Please note, any Procs passed
219
+ # in will automatically be evaluated (For example: :class => lambda { cycle("even", "odd") })
220
+ # [+block+]
221
+ # The default block to render if no such block block that is to be rendered when "blocks.render" is called for this block.
222
+ def render_without_partials(name_or_container, *args, &block)
223
+ options = args.extract_options!
224
+ options[:skip_partials] = true
225
+ args.push(options)
226
+ render(name_or_container, *args, &block)
227
+ end
228
+
229
+ # Render a block, first rendering any "before" blocks, then rendering the block itself, then rendering
230
+ # any "after" blocks. Additionally, a collection may also be passed in, and Blocks will render
231
+ # an the block, along with corresponding before and after blocks for each element of the collection.
232
+ # Blocks will make four different attempts to render block:
233
+ # 1) Look for a block that has been defined inline elsewhere, using the blocks.define method:
234
+ # <% blocks.define :wizard do |options| %>
235
+ # Inline Block Step#<%= options[:step] %>.
236
+ # <% end %>
237
+ #
238
+ # <%= blocks.render :wizard, :step => @step %>
239
+ # 2) Look for a partial within the current controller's view directory:
240
+ # <%= blocks.render :wizard, :step => @step %>
241
+ #
242
+ # <!-- In /app/views/pages/_wizard.html.erb (assuming it is the pages controller running): -->
243
+ # Controller-specific Block Step# <%= step %>.
244
+ # 3) Look for a partial with the global blocks view directory (by default /app/views/blocks/):
245
+ # <%= blocks.render :wizard, :step => @step %>
246
+ #
247
+ # <!-- In /app/views/blocks/_wizard.html.erb: -->
248
+ # Global Block Step#<%= step %>.
249
+ # 4) Render the default implementation for the block if provided to the blocks.render call:
250
+ # <%= blocks.render :wizard, :step => @step do |options| do %>
251
+ # Default Implementation Block Step#<%= options %>.
252
+ # <% end %>
253
+ # Options:
254
+ # [+name_or_container+]
255
+ # The name of the block to render (either a string or a symbol)
256
+ # [+*args+]
257
+ # Any arguments to pass to the block to be rendered (and also to be passed to any "before" and "after" blocks).
258
+ # The last argument in the list can be a hash and can include the following special options:
259
+ # [:collection]
260
+ # The collection of elements to render blocks for
261
+ # [:as]
262
+ # The variable name to assign the current element in the collection being rendered over
263
+ # [:surrounding_tag]
264
+ # The content tag to render around a block, which might be particularly useful when rendering a collection of blocks,
265
+ # such as for a list or table
266
+ # [:surrounding_tag_html]
267
+ # The attributes to be applied to the HTML content tag, such as styling or special properties. Please note, any Procs passed
268
+ # in will automatically be evaluated (For example: :class => lambda { cycle("even", "odd") })
269
+ # [+block+]
270
+ # The default block to render if no such block block that is to be rendered when "blocks.render" is called for this block.
271
+ def render_with_partials(name_or_container, *args, &block)
272
+ options = args.extract_options!
273
+ options[:use_partials] = true
274
+ args.push(options)
275
+ render(name_or_container, *args, &block)
276
+ end
277
+
278
+ # Queue a block for later rendering, such as within a template.
279
+ # <%= Blocks::Base.new(self).render_template("shared/wizard") do |blocks| %>
280
+ # <% blocks.queue :step1 %>
281
+ # <% blocks.queue :step2 do %>
282
+ # My overridden Step 2 |
283
+ # <% end %>
284
+ # <% blocks.queue :step3 %>
285
+ # <% blocks.queue do %>
286
+ # | Anonymous Step 4
287
+ # <% end %>
288
+ # <% end %>
289
+ #
290
+ # <!-- In /app/views/shared/wizard -->
291
+ # <% blocks.define :step1 do %>
292
+ # Step 1 |
293
+ # <% end %>
294
+ #
295
+ # <% blocks.define :step2 do %>
296
+ # Step 2 |
297
+ # <% end %>
298
+ #
299
+ # <% blocks.define :step3 do %>
300
+ # Step 3
301
+ # <% end %>
302
+ #
303
+ # <% blocks.queued_blocks.each do |block| %>
304
+ # <%= blocks.render block %>
305
+ # <% end %>
306
+ #
307
+ # <!-- Will render: Step 1 | My overridden Step 2 | Step 3 | Anonymous Step 4-->
308
+ # Options:
309
+ # [+*args+]
310
+ # The options to pass in when this block is rendered. These will override any options provided to the actual block
311
+ # definition. Any or all of these options may be overriden by whoever calls "blocks.render" on this block.
312
+ # Usually the first of these args will be the name of the block being queued (either a string or a symbol)
313
+ # [+block+]
314
+ # The optional block definition to render when the queued block is rendered
315
+ def queue(*args, &block)
316
+ self.queued_blocks << self.define_block_container(*args, &block)
317
+ nil
318
+ end
319
+
320
+ # Render a partial, treating it as a template, and any code in the block argument will impact how the template renders
321
+ # <%= Blocks::Base.new(self).render_template("shared/wizard") do |blocks| %>
322
+ # <% blocks.queue :step1 %>
323
+ # <% blocks.queue :step2 do %>
324
+ # My overridden Step 2 |
325
+ # <% end %>
326
+ # <% blocks.queue :step3 %>
327
+ # <% blocks.queue do %>
328
+ # | Anonymous Step 4
329
+ # <% end %>
330
+ # <% end %>
331
+ #
332
+ # <!-- In /app/views/shared/wizard -->
333
+ # <% blocks.define :step1 do %>
334
+ # Step 1 |
335
+ # <% end %>
336
+ #
337
+ # <% blocks.define :step2 do %>
338
+ # Step 2 |
339
+ # <% end %>
340
+ #
341
+ # <% blocks.define :step3 do %>
342
+ # Step 3
343
+ # <% end %>
344
+ #
345
+ # <% blocks.queued_blocks.each do |block| %>
346
+ # <%= blocks.render block %>
347
+ # <% end %>
348
+ #
349
+ # <!-- Will render: Step 1 | My overridden Step 2 | Step 3 | Anonymous Step 4-->
350
+ # Options:
351
+ # [+partial+]
352
+ # The partial to render as a template
353
+ # [+block+]
354
+ # An optional block with code that affects how the template renders
355
+ def render_template(partial, &block)
356
+ render_options = global_options.clone
357
+ render_options[self.variable] = self
358
+ render_options[:captured_block] = view.capture(self, &block) if block_given?
359
+
360
+ view.render partial, render_options
361
+ end
362
+
363
+ # Add a block to render before another block. This before block will be put into an array so that multiple
364
+ # before blocks may be queued. They will render in the order in which they are declared when the
365
+ # "blocks#render" method is called. Any options specified to the before block will override any options
366
+ # specified in the block definition.
367
+ # <% blocks.define :wizard, :option1 => 1, :option2 => 2 do |options| %>
368
+ # Step 2 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)<br />
369
+ # <% end %>
370
+ #
371
+ # <% blocks.before :wizard, :option1 => 3 do
372
+ # Step 0 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)<br />
373
+ # <% end %>
374
+ #
375
+ # <% blocks.before :wizard, :option2 => 4 do
376
+ # Step 1 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)<br />
377
+ # <% end %>
378
+ #
379
+ # <%= blocks.render :wizard %>
380
+ #
381
+ # <!-- Will render:
382
+ # Step 0 (:option1 => 3, :option2 => 2)<br />
383
+ # Step 1 (:option1 => 1, :option2 => 4)<br />
384
+ # Step 2 (:option1 => 1, :option2 => 2)<br />
385
+ # -->
386
+ #
387
+ # <%= blocks.render :wizard, :step => @step %>
388
+ # Options:
389
+ # [+name+]
390
+ # The name of the block to render this code before when that block is rendered
391
+ # [+options+]
392
+ # Any options to specify to the before block when it renders. These will override any options
393
+ # specified when the block was defined.
394
+ # [+block+]
395
+ # The block of code to render before another block
396
+ def before(name, options={}, &block)
397
+ self.queue_block_container("before_#{name.to_s}", options, &block)
398
+ nil
399
+ end
400
+ alias prepend before
401
+
402
+ # Add a block to render after another block. This after block will be put into an array so that multiple
403
+ # after blocks may be queued. They will render in the order in which they are declared when the
404
+ # "blocks#render" method is called. Any options specified to the after block will override any options
405
+ # specified in the block definition.
406
+ # <% blocks.define :wizard, :option1 => 1, :option2 => 2 do |options| %>
407
+ # Step 2 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)<br />
408
+ # <% end %>
409
+ #
410
+ # <% blocks.after :wizard, :option1 => 3 do
411
+ # Step 3 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)<br />
412
+ # <% end %>
413
+ #
414
+ # <% blocks.after :wizard, :option2 => 4 do
415
+ # Step 4 (:option1 => <%= options[option1] %>, :option2 => <%= options[option2] %>)<br />
416
+ # <% end %>
417
+ #
418
+ # <%= blocks.render :wizard %>
419
+ #
420
+ # <!-- Will render:
421
+ # Step 2 (:option1 => 1, :option2 => 2)<br />
422
+ # Step 3 (:option1 => 3, :option2 => 2)<br />
423
+ # Step 4 (:option1 => 1, :option2 => 4)<br />
424
+ # -->
425
+ #
426
+ # <%= blocks.render :wizard, :step => @step %>
427
+ # Options:
428
+ # [+name+]
429
+ # The name of the block to render this code after when that block is rendered
430
+ # [+options+]
431
+ # Any options to specify to the after block when it renders. These will override any options
432
+ # specified when the block was defined.
433
+ # [+block+]
434
+ # The block of code to render after another block
435
+ def after(name, options={}, &block)
436
+ self.queue_block_container("after_#{name.to_s}", options, &block)
437
+ nil
438
+ end
439
+ alias append after
440
+ alias for after
441
+
442
+ # Add a block to render around another block. This around block will be put into an array so that multiple
443
+ # around blocks may be queued. They will render in the order in which they are declared when the
444
+ # "blocks#render" method is called, with the last declared around block being rendered as the outer-most code, and
445
+ # the first declared around block rendered as the inner-most code. Any options specified to the after block will override any options
446
+ # specified in the block definition. The user of an around block must declare a block with at least one parameter and
447
+ # should invoke the #call method on that argument.
448
+ #
449
+ # <% blocks.define :my_block do %>
450
+ # test
451
+ # <% end %>
452
+ #
453
+ # <% blocks.around :my_block do |content_block| %>
454
+ # <h1>
455
+ # <%= content_block.call %>
456
+ # </h1>
457
+ # <% end %>
458
+ #
459
+ # <% blocks.around :my_block do |content_block| %>
460
+ # <span style="color:red">
461
+ # <%= content_block.call %>
462
+ # </span>
463
+ # <% end %>
464
+ #
465
+ # <%= blocks.render :my_block %>
466
+ #
467
+ # <!-- Will render:
468
+ # <h1>
469
+ # <span style="color:red">
470
+ # test
471
+ # </span>
472
+ # </h1>
473
+ #
474
+ # Options:
475
+ # [+name+]
476
+ # The name of the block to render this code around when that block is rendered
477
+ # [+options+]
478
+ # Any options to specify to the around block when it renders. These will override any options
479
+ # specified when the block was defined.
480
+ # [+block+]
481
+ # The block of code to render after another block
482
+ def around(name, options={}, &block)
483
+ self.queue_block_container("around_#{name.to_s}", options, &block)
484
+ nil
485
+ end
486
+
487
+ def evaluated_procs(*args)
488
+ options = args.shift.presence || {}
489
+ if options.is_a?(Proc)
490
+ evaluated_proc(options, *args)
491
+ else
492
+ options.inject({}) { |hash, (k, v)| hash[k] = evaluated_proc(v, *args); hash}
493
+ end
494
+ end
495
+
496
+ def evaluated_proc(*args)
497
+ return nil unless args.present?
498
+ v = args.shift
499
+ v.is_a?(Proc) ? v.call(*(args[0, v.arity])) : v
500
+ end
501
+
502
+ protected
503
+
504
+ # If a method is missing, we'll assume the user is starting a new block group by that missing method name
505
+ def method_missing(m, *args, &block)
506
+ options = args.extract_options!
507
+
508
+ # If the specified block group has already been defined, it is simply returned here for iteration.
509
+ # It will consist of all the blocks used in this block group that have yet to be rendered,
510
+ # as the call for their use occurred before the template was rendered (where their definitions likely occurred)
511
+ return self.block_groups[m] unless self.block_groups[m].nil?
512
+
513
+ # Allows for nested block groups, store the current block positions array and start a new one
514
+ original_queued_blocks = self.queued_blocks
515
+ self.queued_blocks = []
516
+ self.block_groups[m] = self.queued_blocks
517
+
518
+ # Capture the contents of the block group (this will only capture block definitions and block renders; it will ignore anything else)
519
+ view.capture(global_options.merge(options), &block) if block_given?
520
+
521
+ # restore the original block positions array
522
+ self.queued_blocks = original_queued_blocks
523
+ nil
524
+ end
525
+
526
+ def initialize(view, options={})
527
+ self.template_folder = options[:template_folder] ? options.delete(:template_folder) : Blocks.template_folder
528
+ self.variable = (options[:variable] ? options.delete(:variable) : :blocks).to_sym
529
+ self.view = view
530
+ self.global_options = options
531
+ self.queued_blocks = []
532
+ self.blocks = {}
533
+ self.anonymous_block_number = 0
534
+ self.block_groups = {}
535
+ self.use_partials = options[:use_partials].nil? ? Blocks.use_partials : options.delete(:use_partials)
536
+ self.surrounding_tag_surrounds_before_and_after_blocks = options[:surrounding_tag_surrounds_before_and_after_blocks].nil? ? Blocks.surrounding_tag_surrounds_before_and_after_blocks : options.delete(:surrounding_tag_surrounds_before_and_after_blocks)
537
+ end
538
+
539
+ # Return a unique name for an anonymously defined block (i.e. a block that has not been given a name)
540
+ def anonymous_block_name
541
+ self.anonymous_block_number += 1
542
+ "block_#{anonymous_block_number}"
543
+ end
544
+
545
+ def render_block_with_around_blocks(name_or_container, *args, &block)
546
+ name = name_or_container.is_a?(Blocks::Container) ? name_or_container.name.to_sym : name_or_container.to_sym
547
+ around_name = "around_#{name.to_s}".to_sym
548
+
549
+ around_blocks = blocks[around_name].present? ? blocks[around_name].clone : []
550
+
551
+ content_block = Proc.new do
552
+ block_container = around_blocks.shift
553
+ if block_container
554
+ view.capture(content_block, *(args[0, block_container.block.arity - 1]), &block_container.block)
555
+ else
556
+ render_block(name_or_container, *args, &block)
557
+ end
558
+ end
559
+ content_block.call
560
+ end
561
+
562
+ # Render a block, first trying to find a previously defined block with the same name
563
+ def render_block(name_or_container, *args, &block)
564
+ options = args.extract_options!
565
+
566
+ buffer = ActiveSupport::SafeBuffer.new
567
+
568
+ block_options = {}
569
+ if (name_or_container.is_a?(Blocks::Container))
570
+ name = name_or_container.name.to_sym
571
+ block_options = name_or_container.options
572
+ else
573
+ name = name_or_container.to_sym
574
+ end
575
+
576
+ if blocks[name]
577
+ block_container = blocks[name]
578
+ args.push(global_options.merge(block_container.options).merge(block_options).merge(options))
579
+ buffer << view.capture(*(args[0, block_container.block.arity]), &block_container.block)
580
+ elsif (use_partials || options[:use_partials]) && !options[:skip_partials]
581
+ begin
582
+ begin
583
+ buffer << view.render("#{name.to_s}", global_options.merge(block_options).merge(options))
584
+ rescue ActionView::MissingTemplate
585
+ buffer << view.render("#{self.template_folder}/#{name.to_s}", global_options.merge(block_options).merge(options))
586
+ end
587
+ rescue ActionView::MissingTemplate
588
+ args.push(global_options.merge(options))
589
+ buffer << view.capture(*(args[0, block.arity]), &block) if block_given?
590
+ end
591
+ else
592
+ args.push(global_options.merge(options))
593
+ buffer << view.capture(*(args[0, block.arity]), &block) if block_given?
594
+ end
595
+
596
+ buffer
597
+ end
598
+
599
+ # Render all the before blocks for a partial block
600
+ def render_before_blocks(name_or_container, *args)
601
+ render_before_or_after_blocks(name_or_container, "before", *args)
602
+ end
603
+
604
+ # Render all the after blocks for a partial block
605
+ def render_after_blocks(name_or_container, *args)
606
+ render_before_or_after_blocks(name_or_container, "after", *args)
607
+ end
608
+
609
+ # Utility method to render either the before or after blocks for a partial block
610
+ def render_before_or_after_blocks(name_or_container, before_or_after, *args)
611
+ options = args.extract_options!
612
+
613
+ block_options = {}
614
+ if (name_or_container.is_a?(Blocks::Container))
615
+ name = name_or_container.name.to_sym
616
+ block_options = name_or_container.options
617
+ else
618
+ name = name_or_container.to_sym
619
+ block_options = blocks[name].options if blocks[name]
620
+ end
621
+
622
+ before_name = "#{before_or_after}_#{name.to_s}".to_sym
623
+ buffer = ActiveSupport::SafeBuffer.new
624
+
625
+ blocks[before_name].each do |block_container|
626
+ args_clone = args.clone
627
+ args_clone.push(global_options.merge(block_options).merge(block_container.options).merge(options))
628
+ buffer << view.capture(*(args_clone[0, block_container.block.arity]), &block_container.block)
629
+ end if blocks[before_name].present?
630
+
631
+ buffer
632
+ end
633
+
634
+ # Build a Blocks::Container object given the passed in arguments
635
+ def build_block_container(*args, &block)
636
+ options = args.extract_options!
637
+ name = args.first ? args.shift : self.anonymous_block_name
638
+ block_container = Blocks::Container.new
639
+ block_container.name = name.to_sym
640
+ block_container.options = options
641
+ block_container.block = block
642
+ block_container
643
+ end
644
+
645
+ # Build a Blocks::Container object and add it to an array of containers matching it's block name
646
+ # (used only for queuing a collection of before and after blocks for a particular block name)
647
+ def queue_block_container(*args, &block)
648
+ block_container = self.build_block_container(*args, &block)
649
+ if blocks[block_container.name].nil?
650
+ blocks[block_container.name] = [block_container]
651
+ else
652
+ blocks[block_container.name] << block_container
653
+ end
654
+ end
655
+
656
+ # Build a Blocks::Container object and add it to the global hash of blocks if a block by the same
657
+ # name is not already defined
658
+ def define_block_container(*args, &block)
659
+ block_container = self.build_block_container(*args, &block)
660
+ blocks[block_container.name] = block_container if blocks[block_container.name].nil? && block_given?
661
+ block_container
662
+ end
663
+
664
+ def content_tag(tag, tag_html, *args, &block)
665
+ if tag
666
+ view.content_tag(tag, block.call, evaluated_procs(tag_html, *args))
667
+ else
668
+ block.call
669
+ end
670
+ end
671
+ end
672
+ end