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.
- checksums.yaml +4 -4
- data/.gitignore +4 -2
- data/.ruby-version +1 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.rdoc +7 -0
- data/Gemfile +10 -3
- data/README.md +25 -9
- data/_config.yml +20 -0
- data/bin/deploy_docs +2 -0
- data/docs/.gitignore +4 -0
- data/docs/404.html +23 -0
- data/docs/_includes/acknowledgements.md +13 -0
- data/docs/_includes/caller-id.md +3 -0
- data/docs/_includes/configuration.md +3 -0
- data/docs/_includes/custom-builders.md +3 -0
- data/docs/_includes/defining.md +615 -0
- data/docs/_includes/demos/hooks-and-wrappers-output.html +109 -0
- data/docs/_includes/hooks.md +1156 -0
- data/docs/_includes/installation.md +25 -0
- data/docs/_includes/introduction.md +18 -0
- data/docs/_includes/option-merging.md +5 -0
- data/docs/_includes/rendering.md +622 -0
- data/docs/_includes/reserved-keywords.md +59 -0
- data/docs/_includes/skipping.md +403 -0
- data/docs/_includes/slate/assets.html +34 -0
- data/docs/_includes/slate/language-tabs.html +11 -0
- data/docs/_includes/templating.md +48 -0
- data/docs/_includes/templating/bootstrap_4_cards.md +753 -0
- data/docs/_includes/use-cases.md +23 -0
- data/docs/_includes/wip.md +34 -0
- data/docs/_includes/wrappers.md +629 -0
- data/docs/_layouts/slate.html +75 -0
- data/docs/_plugins/gem_version.rb +11 -0
- data/docs/_plugins/highlight_with_div.rb +25 -0
- data/docs/_sass/_default_styling.scss +627 -0
- data/docs/_sass/_icon-font.scss +26 -0
- data/docs/_sass/_normalize.scss +427 -0
- data/docs/_sass/_styling_overrides.scss +31 -0
- data/docs/_sass/_syntax.scss +78 -0
- data/docs/_sass/_variable_overrides.scss +10 -0
- data/docs/_sass/_variables.scss +105 -0
- data/docs/assets/javascripts/script.js +18 -0
- data/docs/assets/stylesheets/formChanges.less +106 -0
- data/docs/assets/stylesheets/style.css +46 -0
- data/docs/fonts/slate.eot +0 -0
- data/docs/fonts/slate.svg +14 -0
- data/docs/fonts/slate.ttf +0 -0
- data/docs/fonts/slate.woff +0 -0
- data/docs/fonts/slate.woff2 +0 -0
- data/docs/hooks.html +149 -0
- data/docs/hooks_and_wrappers_demo.html +313 -0
- data/docs/images/favicon.ico +0 -0
- data/docs/images/logo.png +0 -0
- data/docs/images/navbar.png +0 -0
- data/docs/images/placeholder.jpg +0 -0
- data/docs/images/render_strategies.png +0 -0
- data/docs/images/templating.png +0 -0
- data/docs/index.md +32 -0
- data/docs/javascripts/all.js +4 -0
- data/docs/javascripts/all_nosearch.js +3 -0
- data/docs/javascripts/app/lang.js +166 -0
- data/docs/javascripts/app/search.js +75 -0
- data/docs/javascripts/app/toc.js +57 -0
- data/docs/javascripts/demos/hooks_and_wrappers.js +9 -0
- data/docs/javascripts/lib/energize.js +169 -0
- data/docs/javascripts/lib/imagesloaded.min.js +7 -0
- data/docs/javascripts/lib/jquery.highlight.js +108 -0
- data/docs/javascripts/lib/jquery.js +9831 -0
- data/docs/javascripts/lib/jquery.tocify.js +1042 -0
- data/docs/javascripts/lib/jquery_ui.js +566 -0
- data/docs/javascripts/lib/lunr.js +1910 -0
- data/docs/stylesheets/demos/hooks_and_wrappers.scss +32 -0
- data/docs/stylesheets/print.scss +150 -0
- data/docs/stylesheets/screen.scss +10 -0
- data/lib/blocks/action_view_extensions/view_extensions.rb +1 -0
- data/lib/blocks/renderers/renderer.rb +1 -0
- data/lib/blocks/renderers/runtime_context.rb +30 -17
- data/lib/blocks/utilities/hash_with_render_strategy.rb +3 -0
- data/lib/blocks/version.rb +1 -1
- 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>
|