props_template 0.14.0 → 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +205 -140
- data/lib/props_template.rb +3 -9
- data/lib/props_template/base.rb +33 -24
- data/lib/props_template/base_with_extensions.rb +18 -19
- data/lib/props_template/debug_writer.rb +55 -0
- data/lib/props_template/extension_manager.rb +27 -31
- data/lib/props_template/extensions/cache.rb +2 -21
- data/lib/props_template/extensions/deferment.rb +11 -3
- data/lib/props_template/extensions/fragment.rb +4 -28
- data/lib/props_template/extensions/partial_renderer.rb +79 -33
- data/lib/props_template/railtie.rb +0 -8
- data/lib/props_template/searcher.rb +0 -3
- data/lib/props_template/version.rb +3 -0
- data/spec/layout_spec.rb +18 -10
- metadata +15 -26
- data/lib/props_template/key_formatter.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4cf7de015844e77efdf93e02272159080835cad2e0f6022e610690727a05cfb5
|
4
|
+
data.tar.gz: 350d1a534d30c625b7adbe3c95d6ac33c2a831205c970c404bb2c9eeb22dfd69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 533ff82a0dac43e211d9a5800b71ef9ec59a4e8d34d16214a3799ea7398e9746a1d0b823db55bfd4b272ea9a48a9e685a3f49fc437f8b7a7aa1ed49ef2d531c8
|
7
|
+
data.tar.gz: 0fc9fc3e0b1d898c1870c286d14dcd59e8a6679d428c026dcf2c6107ef8e281f40d0e08838f625e1a39b321199510920fe2e3a1b9ff20a08990106220fffc949
|
data/README.md
CHANGED
@@ -1,8 +1,29 @@
|
|
1
1
|
# PropsTemplate
|
2
2
|
|
3
|
-
PropsTemplate is a
|
3
|
+
PropsTemplate is a direct-to-Oj, JBuilder-like DSL for building JSON. It has
|
4
|
+
support for Russian-Doll caching, layouts, and can be queried by giving the
|
5
|
+
root a key path.
|
4
6
|
|
5
|
-
|
7
|
+
[![Build
|
8
|
+
Status](https://circleci.com/gh/thoughtbot/props_template.svg?style=shield)](https://circleci.com/gh/thoughtbot/props_template)
|
9
|
+
|
10
|
+
It's fast.
|
11
|
+
|
12
|
+
PropsTemplate bypasses the steps of hash building and serializing
|
13
|
+
that other libraries perform by using Oj's `StringWriter` in `rails` mode.
|
14
|
+
|
15
|
+
![benchmarks](docs/benchmarks.png)
|
16
|
+
|
17
|
+
Caching is fast too.
|
18
|
+
|
19
|
+
While other libraries spend time unmarshaling,
|
20
|
+
merging hashes, and serializing to JSON; PropsTemplate simply takes
|
21
|
+
the cached string and uses Oj's [push_json](http://www.ohler.com/oj/doc/Oj/StringWriter.html#push_json-instance_method).
|
22
|
+
|
23
|
+
## Example:
|
24
|
+
|
25
|
+
PropsTemplate is very similar to JBuilder, and selectively retains some
|
26
|
+
conveniences and magic.
|
6
27
|
|
7
28
|
```ruby
|
8
29
|
json.flash flash.to_h
|
@@ -40,25 +61,33 @@ json.posts do
|
|
40
61
|
json.total @posts.count
|
41
62
|
end
|
42
63
|
|
43
|
-
|
44
64
|
json.footer partial: 'shared/footer' do
|
45
65
|
end
|
46
66
|
```
|
47
67
|
|
68
|
+
## Installation
|
69
|
+
|
70
|
+
```
|
71
|
+
gem 'props_template'
|
72
|
+
```
|
73
|
+
|
74
|
+
and run `bundle`
|
75
|
+
|
48
76
|
## API
|
49
77
|
|
50
|
-
### json.set! or json
|
51
|
-
|
78
|
+
### json.set! or json.\<your key here\>
|
79
|
+
|
80
|
+
Defines the attribute or structure. All keys are automatically camelized lower.
|
52
81
|
|
53
82
|
```ruby
|
54
|
-
json.set! :author_details, {
|
83
|
+
json.set! :author_details, {...options} do
|
55
84
|
json.set! :first_name, 'David'
|
56
85
|
end
|
57
86
|
|
58
87
|
or
|
59
88
|
|
60
|
-
json.author_details, {
|
61
|
-
json.first_name
|
89
|
+
json.author_details, {...options} do
|
90
|
+
json.first_name 'David'
|
62
91
|
end
|
63
92
|
|
64
93
|
|
@@ -73,6 +102,7 @@ The inline form defines key and value
|
|
73
102
|
| value | A value |
|
74
103
|
|
75
104
|
```ruby
|
105
|
+
|
76
106
|
json.set! :first_name, 'David'
|
77
107
|
|
78
108
|
or
|
@@ -92,39 +122,36 @@ The block form defines key and structure
|
|
92
122
|
|
93
123
|
```ruby
|
94
124
|
json.set! :details do
|
95
|
-
|
125
|
+
...
|
96
126
|
end
|
97
127
|
|
98
128
|
or
|
99
129
|
|
100
130
|
json.details do
|
101
|
-
|
131
|
+
...
|
102
132
|
end
|
103
133
|
```
|
104
134
|
|
105
135
|
The difference between the block form and inline form is
|
106
|
-
1. The block form is an internal node.
|
107
|
-
|
136
|
+
1. The block form is an internal node. Functionality such as Partials,
|
137
|
+
Deferment and other [options](#options) are only available on the
|
138
|
+
block form.
|
139
|
+
2. The inline form is considered a leaf node, and you can only [search](#traversing)
|
140
|
+
for internal nodes.
|
108
141
|
|
109
142
|
### json.array!
|
110
143
|
Generates an array of json objects.
|
111
144
|
|
112
145
|
```ruby
|
113
|
-
collection = [
|
114
|
-
{name: 'john'},
|
115
|
-
{name: 'jim'}
|
116
|
-
]
|
146
|
+
collection = [ {name: 'john'}, {name: 'jim'} ]
|
117
147
|
|
118
148
|
json.details do
|
119
|
-
json.array! collection, {
|
149
|
+
json.array! collection, {...options} do |person|
|
120
150
|
json.first_name person[:name]
|
121
151
|
end
|
122
152
|
end
|
123
153
|
|
124
|
-
# => {"details": [
|
125
|
-
{"firstName": 'john'},
|
126
|
-
{"firstName": 'jim'}
|
127
|
-
]}
|
154
|
+
# => {"details": [{"firstName": 'john'}, {"firstName": 'jim'} ]}
|
128
155
|
```
|
129
156
|
|
130
157
|
| Parameter | Notes |
|
@@ -132,7 +159,8 @@ end
|
|
132
159
|
| collection | A collection that responds to `member_at` and `member_by` |
|
133
160
|
| options | Additional [options](#options)|
|
134
161
|
|
135
|
-
To support [traversing nodes](
|
162
|
+
To support [traversing nodes](#traversing), any list passed
|
163
|
+
to `array!` MUST implement `member_at(index)` and `member_by(attr, value)`.
|
136
164
|
|
137
165
|
For example, if you were using a delegate:
|
138
166
|
|
@@ -153,7 +181,10 @@ end
|
|
153
181
|
Then in your template:
|
154
182
|
|
155
183
|
```ruby
|
156
|
-
data = ObjectCollection.new([
|
184
|
+
data = ObjectCollection.new([
|
185
|
+
{id: 1, name: 'foo'},
|
186
|
+
{id: 2, name: 'bar'}
|
187
|
+
])
|
157
188
|
|
158
189
|
json.array! data do
|
159
190
|
...
|
@@ -184,11 +215,15 @@ end
|
|
184
215
|
|
185
216
|
#### **Array core extension**
|
186
217
|
|
187
|
-
For convenience, PropsTemplate includes a core\_ext that adds these methods to
|
218
|
+
For convenience, PropsTemplate includes a core\_ext that adds these methods to
|
219
|
+
`Array`. For example:
|
188
220
|
|
189
221
|
```ruby
|
190
222
|
require 'props_template/core_ext'
|
191
|
-
data = [
|
223
|
+
data = [
|
224
|
+
{id: 1, name: 'foo'},
|
225
|
+
{id: 2, name: 'bar'}
|
226
|
+
]
|
192
227
|
|
193
228
|
json.posts
|
194
229
|
json.array! data do
|
@@ -197,38 +232,39 @@ json.posts
|
|
197
232
|
end
|
198
233
|
```
|
199
234
|
|
200
|
-
PropsTemplate does not know what the elements are in your collection. The
|
235
|
+
PropsTemplate does not know what the elements are in your collection. The
|
236
|
+
example above will be fine for [traversing](#traversing)
|
237
|
+
by index, but will raise a `NotImplementedError` if you query by attribute. You
|
238
|
+
may still need to implement `member_by`.
|
201
239
|
|
202
240
|
### json.deferred!
|
203
|
-
Returns all deferred nodes used by the [
|
241
|
+
Returns all deferred nodes used by the [deferment](#deferment) option.
|
204
242
|
|
205
|
-
|
206
|
-
json.
|
207
|
-
```
|
208
|
-
|
209
|
-
This method is normally used in `application.json.props` when first generated by `rails breezy:install:web`
|
243
|
+
**Note** This is a [BreezyJS][1] specific functionality and is used in
|
244
|
+
`application.json.props` when first running `rails breezy:install:web`
|
210
245
|
|
211
|
-
### json.fragments!
|
212
|
-
Returns all fragment nodes used by the [partial fragments](#partial-fragments) option.
|
213
246
|
|
214
247
|
```ruby
|
215
|
-
json.
|
216
|
-
```
|
248
|
+
json.deferred json.deferred!
|
217
249
|
|
218
|
-
|
250
|
+
# => [{url: '/some_url?bzq=outer.inner', path: 'outer.inner', type: 'auto'}]
|
251
|
+
```
|
219
252
|
|
220
|
-
|
253
|
+
This method provides metadata about deferred nodes to the frontend ([BreezyJS][1])
|
254
|
+
to fetch missing data in a second round trip.
|
221
255
|
|
222
|
-
|
256
|
+
### json.fragments!
|
257
|
+
Returns all fragment nodes used by the [partial fragments](#partial-fragments)
|
258
|
+
option.
|
223
259
|
|
224
|
-
```ruby
|
225
|
-
# _some_partial.json.props
|
260
|
+
```ruby json.fragments json.fragments! ```
|
226
261
|
|
227
|
-
|
228
|
-
|
262
|
+
**Note** This is a [BreezyJS][1] specific functionality and is used in
|
263
|
+
`application.json.props` when first running `rails breezy:install:web`
|
229
264
|
|
230
265
|
## Options
|
231
|
-
Functionality such as Partials, Deferements, and Caching can only be
|
266
|
+
Options Functionality such as Partials, Deferements, and Caching can only be
|
267
|
+
set on a block. It is normal to see empty blocks.
|
232
268
|
|
233
269
|
```ruby
|
234
270
|
json.post(partial: 'blog_post') do
|
@@ -237,7 +273,9 @@ end
|
|
237
273
|
|
238
274
|
### Partials
|
239
275
|
|
240
|
-
Partials are supported. The following will render the file
|
276
|
+
Partials are supported. The following will render the file
|
277
|
+
`views/posts/_blog_posts.json.props`, and set a local variable `foo` assigned
|
278
|
+
with @post, which you can use inside the partial.
|
241
279
|
|
242
280
|
```ruby
|
243
281
|
json.one_post partial: ["posts/blog_post", locals: {post: @post}] do
|
@@ -247,7 +285,8 @@ end
|
|
247
285
|
Usage with arrays:
|
248
286
|
|
249
287
|
```ruby
|
250
|
-
#
|
288
|
+
# The `as:` option is supported when using `array!`
|
289
|
+
|
251
290
|
json.posts do
|
252
291
|
json.array! @posts, partial: ["posts/blog_post", locals: {foo: 'bar'}, as: 'post'] do
|
253
292
|
end
|
@@ -255,14 +294,14 @@ end
|
|
255
294
|
```
|
256
295
|
|
257
296
|
### Partial Fragments
|
297
|
+
**Note** This is a [BreezyJS][1] specific functionality.
|
258
298
|
|
259
|
-
A fragment
|
260
|
-
|
261
|
-
You would need use partials and add the option `fragment: true`.
|
299
|
+
A fragment identifies a partial output across multiple pages. It can be used to
|
300
|
+
update cross cutting concerns like a header bar.
|
262
301
|
|
263
302
|
```ruby
|
264
303
|
# index.json.props
|
265
|
-
json.header partial: ["profile", fragment:
|
304
|
+
json.header partial: ["profile", fragment: "header"] do
|
266
305
|
end
|
267
306
|
|
268
307
|
# _profile.json.props
|
@@ -276,57 +315,16 @@ end
|
|
276
315
|
When using fragments with Arrays, the argument **MUST** be a lamda:
|
277
316
|
|
278
317
|
```ruby
|
279
|
-
require 'props_template/core_ext'
|
280
|
-
|
281
|
-
json.array! ['foo', 'bar'], partial: ["footer", fragment: ->(x){ x == 'foo'}]
|
282
|
-
```
|
283
|
-
|
284
|
-
PropsTemplate creates a name for the partial using a digest of your locals, partial name, and globalId (to_json as fallback if there is no globalId) on objects that you pass. You may override this behavior and use a custom identifier:
|
318
|
+
require 'props_template/core_ext'
|
285
319
|
|
286
|
-
|
287
|
-
# index.js.breezy
|
288
|
-
json.header partial: ["profile", fragment: 'me_header'] do
|
320
|
+
json.array! ['foo', 'bar'], partial: ["footer", fragment: ->(x){ x == 'foo'}] do
|
289
321
|
end
|
290
322
|
```
|
291
323
|
|
292
|
-
#### Optimisitc Updates
|
293
|
-
Breezy uses the digest generated by `fragment: true` to uniquely identify a partial across the redux state. If you need to optimistically update a fragment, use `json.fragment_digest!` to obtain the identifier in your partial, and `updateFragments` from BreezyJS.
|
294
|
-
|
295
|
-
For example:
|
296
|
-
|
297
|
-
```ruby
|
298
|
-
# _header.js.props
|
299
|
-
json.fragment_digest json.fragment_digest!
|
300
|
-
```
|
301
|
-
|
302
|
-
And in your reducer
|
303
|
-
|
304
|
-
```javacript
|
305
|
-
import {updateFragments} from 'jho406/Breezy';
|
306
|
-
|
307
|
-
switch(action.type) {
|
308
|
-
case SOME_ACTION: {
|
309
|
-
const {
|
310
|
-
fragmentDigest,
|
311
|
-
prevNode // <- the content of the _header.js.props
|
312
|
-
} = action.payload
|
313
|
-
|
314
|
-
const nextNode = {
|
315
|
-
...prevNode,
|
316
|
-
foo: 'bar'
|
317
|
-
}
|
318
|
-
|
319
|
-
return updateFragments(state, {
|
320
|
-
[fragmentDigest]: nextNode
|
321
|
-
})
|
322
|
-
}
|
323
|
-
default:
|
324
|
-
return state
|
325
|
-
}
|
326
|
-
```
|
327
|
-
|
328
324
|
### Caching
|
329
|
-
Caching is supported on
|
325
|
+
Caching is supported on internal nodes only. This limitation is what makes it
|
326
|
+
possible to for props_template to forgo marshalling/unmarshalling and simply
|
327
|
+
use [push_json](http://www.ohler.com/oj/doc/Oj/StringWriter.html#push_json-instance_method).
|
330
328
|
|
331
329
|
Usage:
|
332
330
|
|
@@ -352,44 +350,60 @@ end
|
|
352
350
|
When used with arrays, PropsTemplate will use `Rails.cache.read_multi`.
|
353
351
|
|
354
352
|
```ruby
|
355
|
-
require 'props_template/core_ext'
|
353
|
+
require 'props_template/core_ext'
|
354
|
+
|
355
|
+
opts = { cache: ->(i){ ['a', i] } }
|
356
356
|
|
357
|
-
opts = {
|
358
|
-
cache: ->(i){ ['a', i] }
|
359
|
-
}
|
360
357
|
json.array! [4,5], opts do |x|
|
361
358
|
json.top "hello" + x.to_s
|
362
359
|
end
|
363
360
|
|
364
361
|
#or on arrays with partials
|
365
362
|
|
366
|
-
opts = {
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
json.array! @options, opts
|
363
|
+
opts = { cache: (->(d){ ['a', d.id] }), partial: ["blog_post", as: :blog_post] }
|
364
|
+
|
365
|
+
json.array! @options, opts do
|
366
|
+
end
|
371
367
|
```
|
372
368
|
|
373
369
|
### Deferment
|
374
370
|
|
375
|
-
You can defer rendering of expensive nodes in your content tree using the
|
376
|
-
|
371
|
+
You can defer rendering of expensive nodes in your content tree using the
|
372
|
+
`defer: :manual` option. Behind the scenes PropsTemplates will no-op the block
|
373
|
+
entirely and replace the value with a placeholder. A common use case would be
|
374
|
+
tabbed content that does not load until you click the tab.
|
375
|
+
|
376
|
+
When your client receives the payload, you may issue a second request to the
|
377
|
+
same endpoint to fetch any missing nodes. See [traversing nodes](#traversing)
|
377
378
|
|
378
|
-
|
379
|
+
There is also an `defer: :auto` option that you can use with [BreezyJS][1]. [BreezyJS][1]
|
380
|
+
will use the metadata from `json.deferred!` to issue a `remote` dispatch to fetch
|
381
|
+
the missing node and immutably graft it at the appropriate keypath in your Redux
|
382
|
+
store.
|
379
383
|
|
380
384
|
Usage:
|
381
385
|
|
382
386
|
```ruby
|
383
|
-
json.dashboard(defer: :
|
387
|
+
json.dashboard(defer: :manual) do
|
388
|
+
sleep 10
|
389
|
+
json.some_fancy_metric 42
|
390
|
+
end
|
391
|
+
|
392
|
+
|
393
|
+
# or you can explicitly pass a placeholder
|
394
|
+
|
395
|
+
json.dashboard(defer: [:manual, placeholder: {}]) do
|
384
396
|
sleep 10
|
385
397
|
json.some_fancy_metric 42
|
386
398
|
end
|
387
399
|
```
|
388
400
|
|
389
|
-
A
|
401
|
+
A auto option is available:
|
402
|
+
|
403
|
+
**Note** This is a [BreezyJS][1] specific functionality.
|
390
404
|
|
391
405
|
```ruby
|
392
|
-
json.dashboard(defer: :
|
406
|
+
json.dashboard(defer: :auto) do
|
393
407
|
sleep 10
|
394
408
|
json.some_fancy_metric 42
|
395
409
|
end
|
@@ -401,39 +415,51 @@ Finally in your `application.json.props`:
|
|
401
415
|
json.defers json.deferred!
|
402
416
|
```
|
403
417
|
|
404
|
-
|
405
|
-
If `:manual` is used, PropsTemplate will no-op the block and will not populate `json.deferred!`. Its up to you to [traverse](props-template.md#traversing_nodes) to fetch the node seperately. A common usecase would be tab content that does not load until you click the tab.
|
406
|
-
|
407
418
|
#### Working with arrays
|
408
|
-
The default behavior for deferements is to use the index of the collection to
|
419
|
+
The default behavior for deferements is to use the index of the collection to
|
420
|
+
identify an element.
|
421
|
+
|
422
|
+
**Note** If you are using this library with [BreezyJS][1], the `:auto` options will
|
423
|
+
generate `?_bzq=a.b.c.0.title` for `json.deferred!`.
|
409
424
|
|
410
425
|
If you wish to use an attribute to identify the element. You must:
|
411
|
-
|
412
|
-
|
426
|
+
|
427
|
+
1. Use the `:key` option on `json.array!`. This key refers to an attribute on
|
428
|
+
your collection item, and is used for `defer: :auto` to generate a keypath for
|
429
|
+
[BreezyJS][1]. If you are NOT using BreezyJS, you do not need to do this.
|
430
|
+
|
431
|
+
2. Implement `member_at`, on the [collection](#jsonarray). This will be called
|
432
|
+
by PropsTemplate to when [searching nodes](#traversing)
|
413
433
|
|
414
434
|
For example:
|
415
435
|
|
416
436
|
```ruby
|
417
|
-
require 'props_template/core_ext'
|
418
|
-
|
419
|
-
|
437
|
+
require 'props_template/core_ext'
|
438
|
+
data = [
|
439
|
+
{id: 1, name: 'foo'},
|
440
|
+
{id: 2, name: 'bar'}
|
441
|
+
]
|
420
442
|
|
421
443
|
json.posts
|
422
444
|
json.array! data, key: :some_id do |item|
|
445
|
+
# By using :key, props_template will append `json.some_id item.some_id`
|
446
|
+
# automatically
|
447
|
+
|
423
448
|
json.contact(defer: :auto) do
|
424
449
|
json.address '123 example drive'
|
425
450
|
end
|
426
|
-
|
427
|
-
# json.some_id item.some_id will be appended automatically to the end of the block
|
428
451
|
end
|
429
452
|
end
|
430
453
|
```
|
431
454
|
|
432
|
-
|
455
|
+
If you are using [BreezyJS][1], BreezyJS will, it will automatically kick off
|
456
|
+
`remote(?bzq=posts.some_id=1.contact)` and `remote(?bzq=posts.some_id=2.contact)`.
|
433
457
|
|
434
|
-
|
458
|
+
## Traversing
|
435
459
|
|
436
|
-
PropsTemplate has the ability to walk the tree you build, skipping execution of
|
460
|
+
PropsTemplate has the ability to walk the tree you build, skipping execution of
|
461
|
+
untargeted nodes. This feature is useful for selectively updating your frontend
|
462
|
+
state.
|
437
463
|
|
438
464
|
```ruby
|
439
465
|
traversal_path = ['data', 'details', 'personal']
|
@@ -441,7 +467,7 @@ traversal_path = ['data', 'details', 'personal']
|
|
441
467
|
json.data(search: traversal_path) do
|
442
468
|
json.details do
|
443
469
|
json.employment do
|
444
|
-
...more stuff
|
470
|
+
...more stuff
|
445
471
|
end
|
446
472
|
|
447
473
|
json.personal do
|
@@ -452,25 +478,28 @@ json.data(search: traversal_path) do
|
|
452
478
|
end
|
453
479
|
|
454
480
|
json.footer do
|
455
|
-
|
481
|
+
...
|
456
482
|
end
|
457
483
|
```
|
458
484
|
|
459
|
-
PropsTemplate will
|
485
|
+
PropsTemplate will walk depth first, walking only when it finds a matching key,
|
486
|
+
then executes the associated block, and repeats until it the node is found.
|
487
|
+
The above will output:
|
460
488
|
|
461
489
|
```json
|
462
490
|
{
|
463
|
-
data: {
|
464
|
-
name: 'james',
|
465
|
-
zipCode: 91210
|
491
|
+
"data": {
|
492
|
+
"name": 'james',
|
493
|
+
"zipCode": 91210
|
466
494
|
},
|
467
|
-
footer: {
|
468
|
-
|
495
|
+
"footer": {
|
496
|
+
...
|
469
497
|
}
|
470
498
|
}
|
471
499
|
```
|
472
500
|
|
473
|
-
|
501
|
+
Searching only works with blocks, and will NOT work with Scalars
|
502
|
+
("leaf" values). For example:
|
474
503
|
|
475
504
|
```ruby
|
476
505
|
traversal_path = ['data', 'details', 'personal', 'name'] <- not found
|
@@ -482,12 +511,11 @@ json.data(search: traversal_path) do
|
|
482
511
|
end
|
483
512
|
end
|
484
513
|
end
|
485
|
-
|
486
514
|
```
|
487
515
|
|
488
516
|
## Nodes that do not exist
|
489
517
|
|
490
|
-
Nodes that are not found will
|
518
|
+
Nodes that are not found will remove the branch where search was enabled on.
|
491
519
|
|
492
520
|
```ruby
|
493
521
|
traversal_path = ['data', 'details', 'does_not_exist']
|
@@ -501,17 +529,54 @@ json.data(search: traversal_path) do
|
|
501
529
|
end
|
502
530
|
|
503
531
|
json.footer do
|
504
|
-
|
532
|
+
...
|
505
533
|
end
|
506
|
-
|
507
534
|
```
|
508
535
|
|
509
536
|
The above will render:
|
510
537
|
|
511
|
-
```
|
538
|
+
```json
|
512
539
|
{
|
513
|
-
footer: {
|
540
|
+
"footer": {
|
514
541
|
...
|
515
542
|
}
|
516
543
|
}
|
517
544
|
```
|
545
|
+
|
546
|
+
## Layouts
|
547
|
+
A single layout is supported. To use, create an `application.json.props` in
|
548
|
+
`app/views/layouts`. Here's an example:
|
549
|
+
|
550
|
+
```ruby
|
551
|
+
json.data do
|
552
|
+
# template runs here.
|
553
|
+
yield json
|
554
|
+
end
|
555
|
+
|
556
|
+
json.header do
|
557
|
+
json.greeting "Hello"
|
558
|
+
end
|
559
|
+
|
560
|
+
json.footer do
|
561
|
+
json.greeting "Hello"
|
562
|
+
end
|
563
|
+
|
564
|
+
json.flash flash.to_h
|
565
|
+
```
|
566
|
+
|
567
|
+
**NOTE** PropsTemplate inverts the usual Rails rendering flow. PropsTemplate
|
568
|
+
will render Layout first, then the template when `yield json` is used.
|
569
|
+
|
570
|
+
## Contributing
|
571
|
+
|
572
|
+
See the [CONTRIBUTING] document. Thank you, [contributors]!
|
573
|
+
|
574
|
+
[CONTRIBUTING]: CONTRIBUTING.md
|
575
|
+
[contributors]: https://github.com/thoughtbot/props_template/graphs/contributors
|
576
|
+
|
577
|
+
## Special Thanks
|
578
|
+
|
579
|
+
Thanks to [turbostreamer](https://github.com/malomalo/turbostreamer) for the
|
580
|
+
inspiration.
|
581
|
+
|
582
|
+
[1]: https://github.com/thoughtbot/breezy
|