blocks 3.0.0.rc4 → 3.0.0.rc5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -2
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +3 -0
  5. data/CHANGELOG.rdoc +7 -0
  6. data/Gemfile +10 -3
  7. data/README.md +25 -9
  8. data/_config.yml +20 -0
  9. data/bin/deploy_docs +2 -0
  10. data/docs/.gitignore +4 -0
  11. data/docs/404.html +23 -0
  12. data/docs/_includes/acknowledgements.md +13 -0
  13. data/docs/_includes/caller-id.md +3 -0
  14. data/docs/_includes/configuration.md +3 -0
  15. data/docs/_includes/custom-builders.md +3 -0
  16. data/docs/_includes/defining.md +615 -0
  17. data/docs/_includes/demos/hooks-and-wrappers-output.html +109 -0
  18. data/docs/_includes/hooks.md +1156 -0
  19. data/docs/_includes/installation.md +25 -0
  20. data/docs/_includes/introduction.md +18 -0
  21. data/docs/_includes/option-merging.md +5 -0
  22. data/docs/_includes/rendering.md +622 -0
  23. data/docs/_includes/reserved-keywords.md +59 -0
  24. data/docs/_includes/skipping.md +403 -0
  25. data/docs/_includes/slate/assets.html +34 -0
  26. data/docs/_includes/slate/language-tabs.html +11 -0
  27. data/docs/_includes/templating.md +48 -0
  28. data/docs/_includes/templating/bootstrap_4_cards.md +753 -0
  29. data/docs/_includes/use-cases.md +23 -0
  30. data/docs/_includes/wip.md +34 -0
  31. data/docs/_includes/wrappers.md +629 -0
  32. data/docs/_layouts/slate.html +75 -0
  33. data/docs/_plugins/gem_version.rb +11 -0
  34. data/docs/_plugins/highlight_with_div.rb +25 -0
  35. data/docs/_sass/_default_styling.scss +627 -0
  36. data/docs/_sass/_icon-font.scss +26 -0
  37. data/docs/_sass/_normalize.scss +427 -0
  38. data/docs/_sass/_styling_overrides.scss +31 -0
  39. data/docs/_sass/_syntax.scss +78 -0
  40. data/docs/_sass/_variable_overrides.scss +10 -0
  41. data/docs/_sass/_variables.scss +105 -0
  42. data/docs/assets/javascripts/script.js +18 -0
  43. data/docs/assets/stylesheets/formChanges.less +106 -0
  44. data/docs/assets/stylesheets/style.css +46 -0
  45. data/docs/fonts/slate.eot +0 -0
  46. data/docs/fonts/slate.svg +14 -0
  47. data/docs/fonts/slate.ttf +0 -0
  48. data/docs/fonts/slate.woff +0 -0
  49. data/docs/fonts/slate.woff2 +0 -0
  50. data/docs/hooks.html +149 -0
  51. data/docs/hooks_and_wrappers_demo.html +313 -0
  52. data/docs/images/favicon.ico +0 -0
  53. data/docs/images/logo.png +0 -0
  54. data/docs/images/navbar.png +0 -0
  55. data/docs/images/placeholder.jpg +0 -0
  56. data/docs/images/render_strategies.png +0 -0
  57. data/docs/images/templating.png +0 -0
  58. data/docs/index.md +32 -0
  59. data/docs/javascripts/all.js +4 -0
  60. data/docs/javascripts/all_nosearch.js +3 -0
  61. data/docs/javascripts/app/lang.js +166 -0
  62. data/docs/javascripts/app/search.js +75 -0
  63. data/docs/javascripts/app/toc.js +57 -0
  64. data/docs/javascripts/demos/hooks_and_wrappers.js +9 -0
  65. data/docs/javascripts/lib/energize.js +169 -0
  66. data/docs/javascripts/lib/imagesloaded.min.js +7 -0
  67. data/docs/javascripts/lib/jquery.highlight.js +108 -0
  68. data/docs/javascripts/lib/jquery.js +9831 -0
  69. data/docs/javascripts/lib/jquery.tocify.js +1042 -0
  70. data/docs/javascripts/lib/jquery_ui.js +566 -0
  71. data/docs/javascripts/lib/lunr.js +1910 -0
  72. data/docs/stylesheets/demos/hooks_and_wrappers.scss +32 -0
  73. data/docs/stylesheets/print.scss +150 -0
  74. data/docs/stylesheets/screen.scss +10 -0
  75. data/lib/blocks/action_view_extensions/view_extensions.rb +1 -0
  76. data/lib/blocks/renderers/renderer.rb +1 -0
  77. data/lib/blocks/renderers/runtime_context.rb +30 -17
  78. data/lib/blocks/utilities/hash_with_render_strategy.rb +3 -0
  79. data/lib/blocks/version.rb +1 -1
  80. metadata +70 -2
@@ -0,0 +1,34 @@
1
+ {% assign css_url = '/' | prepend: site.css_dir | prepend: '/' | prepend: include.site_url %}
2
+ {% assign fonts_url = '/' | prepend: site.fonts_dir | prepend: '/' %}
3
+ {% assign js_url = '/' | prepend: site.js_dir | prepend: '/' | prepend: include.site_url %}
4
+ {% assign images_url = '/' | prepend: site.images_dir | prepend: '/' | prepend: include.site_url %}
5
+ <style>
6
+ @font-face {
7
+ font-family: 'slate';
8
+ src:url("{{ 'slate.eot?-syv14m' | prepend: fonts_url }}");
9
+ src:url("{{ 'slate.eot?#iefix-syv14m' | prepend: fonts_url }}") format('embedded-opentype'),
10
+ url("{{ 'slate.woff2?-syv14m' | prepend: fonts_url }}") format('woff2'),
11
+ url("{{ 'slate.woff?-syv14m' | prepend: fonts_url }}") format('woff'),
12
+ url("{{ 'slate.ttf?-syv14m' | prepend: fonts_url }}") format('truetype'),
13
+ url("{{'slate.svg?-syv14m#slate' | prepend: fonts_url }}") format('svg');
14
+ font-weight: normal;
15
+ font-style: normal;
16
+ }
17
+ /* bundle exec rougify style base16.monokai > _sass/_syntax.scss */
18
+ /* Rouge::Themes::Base16::Monokai.render(:scope => '.highlight') */
19
+ </style>
20
+ <link href="{{ 'screen.css' | prepend: css_url }}" media="screen" rel="stylesheet" type="text/css">
21
+ <link href="{{ 'print.css' | prepend: css_url }}" media="print" rel="stylesheet" type="text/css">
22
+ <link rel="icon" type="image/x-icon" href="{{'favicon.ico' | prepend: images_url }}">
23
+
24
+ <script src="{{ 'lib/energize.js' | prepend: js_url }}"></script>
25
+ <script src="{{ 'lib/jquery.js' | prepend: js_url }}"></script>
26
+ <script src="{{ 'lib/jquery_ui.js' | prepend: js_url }}"></script>
27
+ <script src="{{ 'lib/jquery.tocify.js' | prepend: js_url }}"></script>
28
+ <script src="{{ 'lib/imagesloaded.min.js' | prepend: js_url }}"></script>
29
+ <script src="{{ 'lib/lunr.js' | prepend: js_url }}"></script>
30
+ <script src="{{ 'app/lang.js' | prepend: js_url }}"></script>
31
+ {% if page.search %}
32
+ <script src="{{ 'app/search.js' | prepend: js_url }}"></script>
33
+ {% endif %}
34
+ <script src="{{ 'app/toc.js' | prepend: js_url }}"></script>
@@ -0,0 +1,11 @@
1
+ {% if include.languages %}
2
+ <div class="lang-selector">
3
+ {% for lang in include.languages %}
4
+ {% if lang.is_a? Hash %}
5
+ <a href="#" data-language-name="{{ lang.keys.first }}">{{ lang.values.first }}</a>
6
+ {% else %}
7
+ <a href="#" data-language-name="{{ lang }}">{{ lang }}</a>
8
+ {% endif %}
9
+ {% endfor %}
10
+ </div>
11
+ {% endif %}
@@ -0,0 +1,48 @@
1
+ # Templating
2
+
3
+ > Sample Syntax
4
+
5
+ ```erb
6
+ <%= render_with_overrides partial:
7
+ "PATH_TO_PARTIAL" do |builder| %>
8
+ <!-- Perform overrides here
9
+ using the builder.
10
+ Whatever happens here
11
+ happens first. -->
12
+ <% end %>
13
+ ```
14
+
15
+ ```haml
16
+ = render_with_overrides partial: "PATH_TO_PARTIAL" do |b|
17
+ #- Perform overrides here using the builder.
18
+ #- Whatever happens here happens first.
19
+ ```
20
+
21
+ ```ruby
22
+ builder = Blocks::Builder.new(view_context)
23
+ builder.render_with_overrides partial:
24
+ "PATH_TO_PARTIAL" do |builder|
25
+ # Perform overrides here
26
+ # using the builder.
27
+ # Whatever happens here
28
+ # happens first.
29
+ end
30
+ ```
31
+
32
+ Templating is one of the most powerful concepts within the Blocks gem. It is the bedrock on which reusable UI components can be built.
33
+
34
+ A template is nothing more than a Rails partial (in future releases, this concept will likely expand to Ruby blocks as well) that has a reference to an instance of a Blocks::Builder object, and uses it to invoke Blocks functionality. It may consist of multiple block definitions, block render calls, block wrappers and hooks, and other content.
35
+
36
+ By default, a Blocks::Builder instance will be passed in as a variable called "builder", but this can be overridden by specifying the "builder_variable" option. When rendering this template, it should produce either a standard / default definition for your template or a sample output of your template complete with dummy data.
37
+
38
+ <aside class="warning">
39
+ This functionality can be invoked on an existing instance of a Blocks::Builder, but this should be done with caution, as all block definitions will share a namespace. For this reason, it is usually best to invoke the functionality on a new instance of a Blocks::Builder.
40
+ </aside>
41
+
42
+ However, this standard / default / sample definition can be overridden at runtime with an overrides block that will execute before the template is rendered by the code that is rendering the template.
43
+
44
+ There are two ways to invoke this functionality, either using the #render_with_overrides method (also aliased as #with_template) that is injected into ActionView as a helper method, or by calling #render_with_overrides on an existing or new instance of a Blocks::Builder.
45
+
46
+ <img src="{{'/templating.png' | prepend: site.images_dir | prepend: '/'}}" />
47
+
48
+ {% include templating/bootstrap_4_cards.md %}
@@ -0,0 +1,753 @@
1
+ ## Building a Bootstrap 4 Card
2
+
3
+ > According to Bootstrap's documentation, a standard card has the following markup:
4
+
5
+ ```html
6
+ <div class="card" style="width: 20rem;">
7
+ <img class="card-img-top" src="..." alt="Card image cap">
8
+ <div class="card-block">
9
+ <h4 class="card-title">Card title</h4>
10
+ <p class="card-text">
11
+ Some quick example text
12
+ </p>
13
+ <a href="#" class="btn btn-primary">Go somewhere</a>
14
+ </div>
15
+ </div>
16
+ ```
17
+
18
+ Templating is best demonstrated through example. In the following set of iterative examples, a template for rendering a [Bootstrap 4 Card](https://v4-alpha.getbootstrap.com/components/card/) will be defined and expanded upon with Blocks functionality.
19
+
20
+ ### Creating a Template
21
+
22
+ > The following code would be added to a new file located at /app/views/shared/\_card.html.erb:
23
+
24
+ {% highlight erb %}
25
+ <% builder.define :card do %>
26
+ <div class="card" style="width: 20rem;">
27
+ <img class="card-img-top" src="..." alt="Card image cap">
28
+ <div class="card-block">
29
+ <h4 class="card-title">Card title</h4>
30
+ <p class="card-text">
31
+ Some quick example text
32
+ </p>
33
+ <a href="#" class="btn btn-primary">Go somewhere</a>
34
+ </div>
35
+ </div>
36
+ <% end %>
37
+ <%= builder.render :card %>
38
+ {% endhighlight %}
39
+
40
+ > Now, when the template is rendered, it will match the markup above exactly.
41
+
42
+ ```erb
43
+ <%= render_with_overrides partial: "shared/card" %>
44
+ <!-- Since no overrides block is provided, this
45
+ call is synonymous with: -->
46
+ <%= Blocks::Builder.new(self).render(partial: "shared/card") %>
47
+ ```
48
+
49
+ ```haml
50
+ = render_with_overrides partial: "shared/card"
51
+ #- Since no overrides block is provided, this
52
+ #- call is synonymous with:
53
+ = Blocks::Builder.new(self).render(partial: "shared/card")
54
+ ```
55
+
56
+ ```ruby
57
+ builder = Blocks::Builder.new(view_context)
58
+ builder.render_with_overrides partial: "shared/card"
59
+ # Since no overrides block is provided, this
60
+ # call is synonymous with:
61
+ builder.render partial: "shared/card"
62
+ ```
63
+
64
+ Setting up a Template is simple. Simply create a Rails partial. That's the bare minimum that needs to be done, although an empty partial won't do anything useful.
65
+
66
+ In this example, we define a block called :card and then render it using the "builder" variable that is automatically defined within the partial.
67
+
68
+ Our output will match the sample markup above but that is because we have hardcoded the markup within the :card block definition. We'll need to make the block definitions more dynamic but before we do that, we should extract out more block definitions.
69
+
70
+ ### Extracting Out Block Definitions
71
+
72
+ > /app/views/shared/\_card.html.erb:
73
+
74
+ {% highlight erb %}
75
+ <% builder.define :card do %>
76
+ <div class="card" style="width: 20rem;">
77
+ <%= builder.render :card_image %>
78
+ <%= builder.render :card_content %>
79
+ </div>
80
+ <% end %>
81
+
82
+ <% builder.define :card_image do %>
83
+ <img class="card-img-top" src="..." alt="Card image cap">
84
+ <% end %>
85
+
86
+ <% builder.define :card_block do %>
87
+ <div class="card-block">
88
+ <%= builder.render :card_title %>
89
+ <%= builder.render :card_text %>
90
+ <%= builder.render :card_action %>
91
+ </div>
92
+ <% end %>
93
+
94
+ <% builder.define :card_title do %>
95
+ <h4 class="card-title">Card title</h4>
96
+ <% end %>
97
+
98
+ <% builder.define :card_text do %>
99
+ <p class="card-text">
100
+ Some quick example text
101
+ </p>
102
+ <% end %>
103
+
104
+ <% builder.define :card_action do %>
105
+ <a href="#" class="btn btn-primary">Go somewhere</a>
106
+ <% end %>
107
+
108
+ <% builder.define :card_content,
109
+ with: :card_block %>
110
+
111
+ <%= builder.render :card %>
112
+ {% endhighlight %}
113
+
114
+ > The output from rendering this template will be the same as before
115
+
116
+ If you look at the sample markup for a card, hopefully you notice a pattern. It's something like this:
117
+
118
+ * A Card is an element with an associated CSS class and is made up of a card image and card content
119
+ * A Card image is an image tag with an associated CSS class and an image path
120
+ * Card Content is an element with an associated CSS class and is made up of a card title, card text, and a card action
121
+ * A Card title is a h4 tag with an associated CSS class and text for the title
122
+ * Card text is a p tag with an associated CSS class and text
123
+ * A Card Action is a link button with associated CSS classes, a label for the button, and a path for the action.
124
+
125
+ While not every Bootstrap 4 card will follow this exact pattern, it is a good starting point for beginning to break down a card into pieces.
126
+
127
+ The code to the right breaks down the main :card block into pieces. Now, the :card block defines the card element and renders its two components: :card_image and :card_card.
128
+
129
+ The :card_image block renders the hardcoded image tag and the :card_content block sets itself up to proxy to the :card_block block. This is done in anticipation (based on having read ahead in the Bootstrap 4 Card documentation) of using something of than a card-block for the content of the card (more on this shortly).
130
+
131
+ ### Extracting out the Wrappers
132
+
133
+ > /app/views/shared/\_card.html.erb:
134
+
135
+ {% highlight erb %}
136
+ <% builder.define :block_wrapper,
137
+ defaults: {
138
+ wrapper_tag: :div,
139
+ wrapper_html: {},
140
+ wrapper_option: :wrapper_html
141
+ } do |block, options| %>
142
+ <%= content_tag options[:wrapper_tag],
143
+ options[options[:wrapper_option]],
144
+ &block %>
145
+ <% end %>
146
+
147
+ <% builder.define :card,
148
+ wrapper: :block_wrapper,
149
+ wrapper_option: :card_html,
150
+ defaults: {
151
+ card_html: { class: "card" },
152
+ } do %>
153
+ <%= builder.render :card_image %>
154
+ <%= builder.render :card_content %>
155
+ <% end %>
156
+
157
+ <% builder.define :card_image do %>
158
+ <img class="card-img-top" src="..." alt="Card image cap">
159
+ <% end %>
160
+
161
+ <% builder.define :card_block,
162
+ wrapper: :block_wrapper,
163
+ wrapper_option: :card_content_html,
164
+ defaults: {
165
+ card_content_html: {
166
+ class: "card-block"
167
+ }
168
+ } do %>
169
+ <%= builder.render :card_title %>
170
+ <%= builder.render :card_text %>
171
+ <%= builder.render :card_action %>
172
+ <% end %>
173
+
174
+ <% builder.define :card_title,
175
+ wrapper: :block_wrapper,
176
+ wrapper_option: :card_title_html,
177
+ wrapper_tag: :h4,
178
+ defaults: {
179
+ card_title_html: {
180
+ class: "card-title",
181
+ }
182
+ } do %>
183
+ Card title
184
+ <% end %>
185
+
186
+ <% builder.define :card_text,
187
+ wrapper: :block_wrapper,
188
+ wrapper_option: :card_text_html,
189
+ wrapper_tag: :p,
190
+ defaults: {
191
+ card_text_html: { class: "card-text" }
192
+ } do %>
193
+ Some quick example text
194
+ <% end %>
195
+
196
+ <% builder.define :card_action do %>
197
+ <a href="#" class="btn btn-primary">Go somewhere</a>
198
+ <% end %>
199
+
200
+ <% builder.define :card_content,
201
+ with: :card_block %>
202
+
203
+ <%= builder.render :card %>
204
+ {% endhighlight %}
205
+
206
+ > The output from rendering this template will be the same as before
207
+
208
+ Perhaps it will also be noticed that every block defined renders an HTML element with nested content. This code is ripe for extraction into a common wrapper block.
209
+
210
+ In the code to the right, this is exactly what is happening. A new block is defined called :block_wrapper. The :block_wrapper block is setup to take a content_block as its first argument, which will automatically be passed in when :block_wrapper is used as a wrapper for another block. :block_wrapper also defines a couple default options, such as :wrapper_tag being set to :div, :wrapper_html being set to an empty hash, and :wrapper_option being set to :wrapper_html. These options are all used within the :block_wrapper definition, where it builds an HTML element around the content_block. The element will be the :wrapper_tag option type and have attributes specified by the option specified by the :wrapper_option option, which will be :wrapper_html by default.
211
+
212
+ Because all of :block_wrapper's options are default options, they are easily overridden by the block being wrapper. For example, the :card block sets wrapper to :block_wrapper and its :wrapper_option to :card_html. Then it sets its default options for :card_html to { class: "card" }. By setting :card_html as a default option for :card, it can easily be overridden by the "render_with_overrides" call that will be demonstrated shortly.
213
+
214
+ <aside class="notice">Notice that :card did not need to declare it's :wrapper_tag as :div since it is already being defaulted to :div by :block_wrapper</aside>
215
+
216
+ :card_block, :card_title, and :card_text each follow the same paradigm. Notice that :card_text overrides the :wrapper_tag to :p and :card_title overrides it to :h4.
217
+
218
+ <aside class="notice">This paradigm of using a block wrapper that wraps a block within an HTML element is so common, that Blocks provides a similar block by default called :content_tag_wrapper (also accessible as the constant Blocks::Builder::CONTENT_TAG_WRAPPER_BLOCK). If this automatically defined block is used instead, the code to the right would only need to change by removing the :block_wrapper definition and changing all references to :block_wrapper to Blocks::Builder::CONTENT_TAG_WRAPPER_BLOCK.</aside>
219
+
220
+ <aside class="notice">The only two blocks that are not using the :block_wrapper are :card_image and :card_action. While both could utilize the :block_wrapper wrapper, it makes more sense not to do so. This will enable us to utilize Rails' link_to and image_tag helper methods instead, since links and images need a bit more fine-tuning than regular HTML elements.</aside>
221
+
222
+
223
+ ### Making the Blocks more Dynamic
224
+
225
+ > /app/views/shared/\_card.html.erb:
226
+
227
+ {% highlight erb %}
228
+ <% builder.define :block_wrapper,
229
+ defaults: {
230
+ wrapper_tag: :div,
231
+ wrapper_html: {},
232
+ wrapper_option: :wrapper_html
233
+ } do |block, options| %>
234
+ <%= content_tag options[:wrapper_tag],
235
+ options[options[:wrapper_option]],
236
+ &block %>
237
+ <% end %>
238
+
239
+ <% builder.define :card,
240
+ wrapper: :block_wrapper,
241
+ wrapper_option: :card_html,
242
+ defaults: {
243
+ card_html: { class: "card" },
244
+ } do %>
245
+ <%= builder.render :card_image %>
246
+ <%= builder.render :card_content %>
247
+ <% end %>
248
+
249
+ <% builder.define :card_image,
250
+ defaults: {
251
+ card_image: "placeholder.jpg",
252
+ card_image_html: { class: "card-img-top" }
253
+ } do |options| %>
254
+ <%= image_tag options[:card_image],
255
+ options[:card_image_html] %>
256
+ <% end %>
257
+
258
+ <% builder.define :card_block,
259
+ wrapper: :block_wrapper,
260
+ wrapper_option: :card_block_html,
261
+ defaults: {
262
+ card_block_html: {
263
+ class: "card-block"
264
+ }
265
+ } do %>
266
+ <%= builder.render :card_title %>
267
+ <%= builder.render :card_text %>
268
+ <%= builder.render :card_action %>
269
+ <% end %>
270
+
271
+ <% builder.define :card_title,
272
+ wrapper: :block_wrapper,
273
+ wrapper_option: :card_title_html,
274
+ wrapper_tag: :h4,
275
+ defaults: {
276
+ card_title_html: {
277
+ class: "card-title",
278
+ },
279
+ card_title: "Card title"
280
+ } do |options| %>
281
+ <%= options[:card_title] %>
282
+ <% end %>
283
+
284
+ <% builder.define :card_text,
285
+ wrapper: :block_wrapper,
286
+ wrapper_option: :card_text_html,
287
+ wrapper_tag: :p,
288
+ defaults: {
289
+ card_text_html: { class: "card-text" },
290
+ card_text: "Some quick example text"
291
+ } do |options| %>
292
+ <%= options[:card_text] %>
293
+ <% end %>
294
+
295
+ <% builder.define :card_action,
296
+ defaults: {
297
+ card_action_path: '#',
298
+ card_action_text: 'Go somewhere',
299
+ card_action_html: { class: "btn btn-primary" }
300
+ } do |options| %>
301
+ <%= link_to options[:card_action_text],
302
+ options[:card_action_path],
303
+ options[:card_action_html] %>
304
+ <% end %>
305
+
306
+ <% builder.define :card_content,
307
+ with: :card_block %>
308
+
309
+ <%= builder.render :card %>
310
+ {% endhighlight %}
311
+
312
+ > The output from rendering this template will be the same as before
313
+
314
+ To round out the Bootstrap 4 Card template, we now specify each block definition that requires dynamic content in it's definition to take the options hash as a parameter. Any hardcoded context is then moved into the defaults hash for that block as an option and the Blocks gem will take. Where the hardcoded content previously was, we can now replace with dynamic code that utilizes the options hash that is passed in.
315
+
316
+ Now we have a more or less complete template (though lacking in several features described in the Bootstrap 4 documentation) for rendering and customizing Bootstrap 4 Cards.
317
+
318
+ ### Rendering the Template with Option Overrides
319
+
320
+ ```erb
321
+ <%= render_with_overrides partial: "shared/card",
322
+ card_html: { id: "my-card" },
323
+ card_action_text: "Go",
324
+ card_action_html: { class: "btn btn-danger" },
325
+ card_title: "My Title",
326
+ card_text: "My Text",
327
+ card_action_path: 'http://mobilecause.com',
328
+ card_image: "my-image.png" %>
329
+ ```
330
+
331
+ ```haml
332
+ = render_with_overrides partial: "shared/card",
333
+ card_html: { id: "my-card" },
334
+ card_action_text: "Go",
335
+ card_action_html: { class: "btn btn-danger" },
336
+ card_title: "My Title",
337
+ card_text: "My Text",
338
+ card_action_path: 'http://mobilecause.com',
339
+ card_image: "my-image.png"
340
+ ```
341
+
342
+ ```ruby
343
+ builder = Blocks::Builder.new(view_context,
344
+ card_html: { id: "my-card" },
345
+ card_action_text: "Go",
346
+ card_action_html: { class: "btn btn-danger" },
347
+ card_title: "My Title",
348
+ card_text: "My Text",
349
+ card_action_path: 'http://mobilecause.com',
350
+ card_image: "my-image.png")
351
+ builder.render_with_overrides partial: "shared/card"
352
+ ```
353
+
354
+ > The above code will output the following:
355
+
356
+ ```html
357
+ <div class="card" id="my-card">
358
+ <img class="card-img-top"
359
+ src="/images/my-image.png"
360
+ alt="My image" />
361
+
362
+ <div class="card-block">
363
+ <h4 class="card-title">
364
+ My Title
365
+ </h4>
366
+ <p class="card-text">
367
+ My Text
368
+ </p>
369
+ <a class="btn btn-danger"
370
+ href="http://mobilecause.com">
371
+ Go
372
+ </a>
373
+ </div>
374
+ </div>
375
+ ```
376
+
377
+ Now that the template is defined, we can start rendering it with actual overrides. Since many of the blocks had some of their options defined as defaults, they can easily be overridden by the render_with_overrides options.
378
+
379
+ <aside class="warning">
380
+ When calling the #render_with_overrides helper method from the view, any options that are passed in when automatically be passed to a new instance of a Blocks::Builder as init options. Therefore, if you're the one initializing the Blocks::Builder object (as is demonstrated in the Ruby example to the right), the options will need to be specified to the Blocks::Builder#new method instead of to the subsequent render_with_overrides call on the Blocks::Builder instance. This will likely be fixed in future releases.
381
+ </aside>
382
+
383
+ <aside class="notice">
384
+ Notice also that the wrapper div maintained both the default class and the provided style. This is because the card_html options were deep merged and there was no clash in keys.
385
+ </aside>
386
+
387
+ ### Rendering the Template with Block Overrides
388
+
389
+ ```erb
390
+ <%= render_with_overrides partial:
391
+ "shared/card" do |builder| %>
392
+ <% builder.define :card do %>
393
+ I am a complete replacement for the card
394
+ <% end %>
395
+ <% end %>
396
+
397
+ <%= render_with_overrides partial:
398
+ "shared/card" do |builder| %>
399
+ <%# Change card_title's tag to h2 %>
400
+ <% builder.define :card_title,
401
+ wrapper_tag: :h2,
402
+ card_title: "I had my wrapper tag changed" %>
403
+
404
+ <%# Change card_action's definition completely %>
405
+ <% builder.define :card_action do |options| %>
406
+ <button onclick="alert('clicked');">
407
+ <%= options[:card_action_text] %>
408
+ </button>
409
+ <% end %>
410
+ <%# turn off card_text's wrapper %>
411
+ <%# and change it's definition %>
412
+ <% builder.define :card_text, wrapper: nil do %>
413
+ This is custom card text.
414
+ <% end %>
415
+ <% end %>
416
+ ```
417
+
418
+ ```haml
419
+ = render_with_overrides partial: "shared/card" do |builder|
420
+ - builder.define :card do
421
+ I am a complete replacement for the card
422
+
423
+ = render_with_overrides partial: "shared/card" do |builder|
424
+ -# Change card_title's tag to h2
425
+ - builder.define :card_title,
426
+ wrapper_tag: :h2,
427
+ card_title: "I had my wrapper tag changed"
428
+
429
+ -# Change card_action's definition completely
430
+ - builder.define :card_action do |options|
431
+ %button{onclick: "alert('clicked');"}
432
+ = options[:card_action_text]
433
+ -# turn off card_text's wrapper
434
+ -# and change it's definition
435
+ - builder.define :card_text,
436
+ wrapper: nil do
437
+ This is custom card text.
438
+ ```
439
+
440
+ ```ruby
441
+ builder = Blocks::Builder.new(view_context)
442
+ text = builder.render_with_overrides partial:
443
+ "shared/card" do |builder|
444
+ builder.define :card do
445
+ "I am a complete replacement for the card"
446
+ end
447
+ end
448
+
449
+ builder = Blocks::Builder.new(view_context)
450
+ text2 = builder.render_with_overrides partial:
451
+ "shared/card" do |builder|
452
+ # Change card_title's tag to h2
453
+ builder.define :card_title,
454
+ wrapper_tag: :h2,
455
+ card_title: "I had my wrapper tag changed"
456
+
457
+ # Change card_action's definition completely
458
+ builder.define :card_action do |options|
459
+ %%"<button onclick='alert('clicked');'>
460
+ #{options[:card_action_text]}
461
+ </button>%.html_safe
462
+ end
463
+ # turn off card_text's wrapper
464
+ # and change it's definition
465
+ builder.define :card_text, wrapper: nil do
466
+ "This is custom card text."
467
+ end
468
+ end
469
+ text + text2
470
+ ```
471
+
472
+ > The above code will output the following:
473
+
474
+ ```html
475
+ <div class="card">
476
+ I am a complete replacement for the card
477
+ </div>
478
+ <div class="card">
479
+ <img class="card-img-top"
480
+ src="/assets/placeholder.jpg"
481
+ alt="Placeholder">
482
+ <div class="card-block">
483
+ <h2 class="card-title">
484
+ I had my wrapper tag changed
485
+ </h2>
486
+ This is custom card text.
487
+ <button onclick="alert('clicked');">
488
+ Go somewhere
489
+ </button>
490
+ </div>
491
+ </div>
492
+ ```
493
+
494
+ Finding the option overrides aren't enough? There's always block overrides. Any block defined within the template can be overridden by an overrides block that executes before the template is rendered.
495
+
496
+ ### Hooking and Skipping Template Definitions
497
+
498
+ > The following code would be added to /app/views/shared/\_card.html.erb, just before the builder.render :card call:
499
+
500
+ {% highlight erb %}
501
+ <% builder.define :card_subtitle,
502
+ wrapper: :block_wrapper,
503
+ wrapper_option: :card_subtitle_html,
504
+ wrapper_tag: :h6,
505
+ defaults: {
506
+ card_subtitle_html: {
507
+ class: "card-subtitle mb-2 text-muted"
508
+ },
509
+ card_subtitle: "Card subtitle"
510
+ } do |options| %>
511
+ <%= options[:card_subtitle] %>
512
+ <% end %>
513
+
514
+ <% builder.define :card_list_group,
515
+ wrapper: :block_wrapper,
516
+ wrapper_option: :card_list_group_html,
517
+ wrapper_tag: :ul,
518
+ defaults: {
519
+ card_list_group_html: {
520
+ class: "list-group list-group-flush"
521
+ }
522
+ } %>
523
+
524
+ <% builder.define :card_list_group_item,
525
+ wrapper: :block_wrapper,
526
+ wrapper_option: :card_list_group_item_html,
527
+ wrapper_tag: :li,
528
+ defaults: {
529
+ card_list_group_item_html: {
530
+ class: "list-group-item"
531
+ },
532
+ card_list_group_item: "Item"
533
+ } do |options| %>
534
+ <%= options[:card_list_group_item] %>
535
+ <% end %>
536
+
537
+ <% builder.define :card_header,
538
+ wrapper: :block_wrapper,
539
+ wrapper_option: :card_header_html,
540
+ defaults: {
541
+ card_header_html: {
542
+ class: "card-header"
543
+ },
544
+ card_header: "Card Header"
545
+ } do |options| %>
546
+ <%= options[:card_header] %>
547
+ <% end %>
548
+
549
+ <% builder.define :card_footer,
550
+ wrapper: :block_wrapper,
551
+ wrapper_option: :card_footer_html,
552
+ defaults: {
553
+ card_footer_html: {
554
+ class: "card-footer"
555
+ },
556
+ card_footer: "Card Footer"
557
+ } do |options| %>
558
+ <%= options[:card_footer] %>
559
+ <% end %>
560
+ {% endhighlight %}
561
+
562
+ ```erb
563
+ <%= render_with_overrides partial:
564
+ "shared/card" do |builder| %>
565
+ <% builder.skip :card_image %>
566
+ <% builder.after :card_title do %>
567
+ <%= builder.render :card_subtitle %>
568
+ <% end %>
569
+ <% builder.prepend :card do %>
570
+ <%= builder.render :card_header %>
571
+ <% end %>
572
+ <% builder.append :card do %>
573
+ <%= builder.render :card_footer %>
574
+ <% end %>
575
+ <% builder.define :card_content,
576
+ with: :card_list_group %>
577
+ <% builder.append :card_content do %>
578
+ <%= builder.render :card_list_group_item,
579
+ card_list_group_item: "Item 1" %>
580
+ <% end %>
581
+
582
+ <% builder.prepend :card_content do %>
583
+ <%= builder.render :card_list_group_item,
584
+ card_list_group_item: "Item 0" %>
585
+ <% end %>
586
+ <% end %>
587
+ ```
588
+
589
+ ```haml
590
+ = render_with_overrides partial: "shared/card" do |builder|
591
+ - builder.skip :card_image
592
+ - builder.after :card_title do
593
+ = builder.render :card_subtitle
594
+ - builder.prepend :card do
595
+ = builder.render :card_header
596
+ - builder.append :card do
597
+ = builder.render :card_footer
598
+ - builder.define :card_content,
599
+ with: :card_list_group
600
+ - builder.append :card_content do
601
+ = builder.render :card_list_group_item,
602
+ card_list_group_item: "Item 1"
603
+ - builder.prepend :card_content do
604
+ = builder.render :card_list_group_item,
605
+ card_list_group_item: "Item 0"
606
+ ```
607
+
608
+ ```ruby
609
+ builder = Blocks::Builder.new(view_context)
610
+ builder.render_with_overrides partial:
611
+ "shared/card" do |builder|
612
+ builder.skip :card_image
613
+ builder.after :card_title do
614
+ builder.render :card_subtitle
615
+ end
616
+ builder.prepend :card do
617
+ builder.render :card_header
618
+ end
619
+ builder.append :card do
620
+ builder.render :card_footer
621
+ end
622
+ builder.define :card_content,
623
+ with: :card_list_group
624
+ builder.append :card_content do
625
+ builder.render :card_list_group_item,
626
+ card_list_group_item: "Item 1"
627
+ end
628
+
629
+ builder.prepend :card_content do
630
+ builder.render :card_list_group_item,
631
+ card_list_group_item: "Item 0"
632
+ end
633
+ end
634
+ ```
635
+
636
+ > The above code will output the following:
637
+
638
+ ```html
639
+ <div class="card">
640
+ <div class="card-header">
641
+ Card Header
642
+ </div>
643
+ <ul class="list-group list-group-flush">
644
+ <li class="list-group-item">
645
+ Item 0
646
+ </li>
647
+ <li class="list-group-item">
648
+ Item 1
649
+ </li>
650
+ </ul>
651
+ <div class="card-footer">
652
+ Card Footer
653
+ </div>
654
+ </div>
655
+ ```
656
+ Scanning through the Bootstrap 4 Cards documentation, it can be plainly observed that the current card template only scratches the surface of available features for cards. In the code to the right, a few of these features are added to the template: :card_subtitle, :card_list_group with :card_list_group_item's, :card_header, and :card_footer. Though all of these features are defined as blocks, none of them are actually used by default. This is actually a good approach, in that these additional features can be added or swapped in as desired while the default output will be unaffected.
657
+
658
+ We also have access to the full arsenal of hooks, wrapper with relation to the various block definitions within the template. We can also skip blocks or replace the definitions with new definitions.
659
+
660
+ ### Extending the Template
661
+
662
+ > Assume the following partial exists in /app/views/shared/\_team_card.html.erb:
663
+
664
+ {% highlight erb %}
665
+ <%= builder.render_with_overrides partial:
666
+ "shared/card" do |builder| %>
667
+ <% builder.define :card_title,
668
+ card_title: team.name %>
669
+ <% builder.define :card_text,
670
+ card_text: team.description %>
671
+ <% builder.define :card_action,
672
+ card_action_text: 'Donate' %>
673
+ <% end %>
674
+ {% endhighlight %}
675
+
676
+ > Then when the following code runs:
677
+
678
+ ```erb
679
+ <% team = OpenStruct.new(
680
+ name: "Andrew's Campaign",
681
+ npo: "Innogive",
682
+ description: "Donate money"
683
+ ) %>
684
+ <%= render_with_overrides partial:
685
+ "shared/team_card",
686
+ team: team do |builder| %>
687
+ <% builder.after :card_title,
688
+ with: :card_subtitle,
689
+ card_subtitle: team.npo %>
690
+ <% end %>
691
+ ```
692
+
693
+ ```haml
694
+ - team = OpenStruct.new(name: "Andrew's Campaign",
695
+ npo: "Innogive",
696
+ description: "Donate money")
697
+ = render_with_overrides partial: "shared/team_card",
698
+ team: team do |builder|
699
+ - builder.after :card_title do
700
+ = builder.render :card_subtitle,
701
+ card_subtitle: team.npo
702
+ ```
703
+
704
+ ```ruby
705
+ team = OpenStruct.new(
706
+ name: "Andrew's Campaign",
707
+ npo: "Innogive",
708
+ description: "Donate money"
709
+ )
710
+ builder = Blocks::Builder.new(view_context)
711
+ builder.render_with_overrides partial:
712
+ "shared/team_card",
713
+ team: team do |builder|
714
+ builder.after :card_title do
715
+ builder.render :card_subtitle,
716
+ card_subtitle: team.npo
717
+ end
718
+ end
719
+ ```
720
+
721
+ > It will produce the following output:
722
+
723
+ ```html
724
+ <div class="card">
725
+ <img class="card-img-top"
726
+ src="/assets/placeholder.jpg"
727
+ alt="Placeholder">
728
+ <div class="card-block">
729
+ <h4 class="card-title">
730
+ Andrew's Campaign
731
+ </h4>
732
+ <h6 class="card-subtitle mb-2 text-muted">
733
+ Innogive
734
+ </h6>
735
+ <p class="card-text">
736
+ Donate money
737
+ </p>
738
+ <a class="btn btn-primary" href="#">
739
+ Donate
740
+ </a>
741
+ </div>
742
+ </div>
743
+ ```
744
+
745
+ Templates may also be extended to form new templates, which may also be extended as many times as necessary.
746
+
747
+ An example might be create new templates that render different versions of a card. For example, maybe one type of card is detailed, while another is an overview. Or maybe one type of card displays information about a team and another about an organization. Or maybe one is meant for conveying information on a dashboard while the other in meant purely for frontend pages. Perhaps then the dashboard card template could be extended multiple times as well to represent different types of objects that may be displayed on the dashboard.
748
+
749
+ In the example to the right, a team-specific version of card is created as a template. Code then renders this new template with some overrides of if its own. The overrides have been kept very basic in order to clearly demonstrate how to setup a template that extends another template.
750
+
751
+ <aside class="warning">
752
+ Take note that with the team_card template, builder.render_with_overrides is used instead of just render_with_overrides. This is forcing the two templates to render using the same Blocks::Builder instance, i.e. to share a Blocks namespace. This is what allows the overrides block for the team_card template to affect the defaults in card template.
753
+ </aside>