actionview-component 1.6.1 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE +25 -0
- data/.github/PULL_REQUEST_TEMPLATE +17 -0
- data/.github/workflows/ruby_on_rails.yml +2 -2
- data/.gitignore +2 -0
- data/.rubocop.yml +4 -0
- data/CHANGELOG.md +62 -0
- data/CONTRIBUTING.md +5 -14
- data/Gemfile.lock +1 -1
- data/README.md +264 -2
- data/lib/action_view/component.rb +1 -0
- data/lib/action_view/component/base.rb +89 -37
- data/lib/action_view/component/preview.rb +7 -15
- data/lib/action_view/component/railtie.rb +7 -2
- data/lib/action_view/component/template_error.rb +11 -0
- data/lib/action_view/component/test_helpers.rb +1 -1
- data/lib/action_view/component/version.rb +2 -2
- data/lib/rails/generators/component/component_generator.rb +2 -2
- data/lib/rails/generators/rspec/component_generator.rb +1 -1
- data/lib/rails/generators/test_unit/component_generator.rb +1 -1
- data/lib/railties/lib/rails.rb +0 -1
- data/lib/railties/lib/rails/components_controller.rb +5 -1
- data/lib/railties/lib/rails/templates/rails/components/preview.html.erb +1 -1
- metadata +5 -4
- data/lib/railties/lib/rails/component_examples_controller.rb +0 -9
- data/lib/railties/lib/rails/templates/rails/examples/show.html.erb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67d62ba2c307963de0523a13f554d1e78b9e76990ace76c36b7d982ec83ef2cb
|
4
|
+
data.tar.gz: 66682a57cd5504b054c7c21227bbfb6cdc2c14f0ac151f2d6bb136a1f2e7a6cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f5b33cb1dfff517ae58afb9167a5f0370f40902eef1ecfef1d6ee0e9f589a255826b92cbccf3bec5fd0a91d487601ec273f6238ddc029a3daade14aa5cd8a5a
|
7
|
+
data.tar.gz: f2f58364fba2269e40bf5bd1040872f826365f67e09679996c79999a0755a1ffdbe06f89bb661f80e7a23a2b4791dff6618b28da9a9cf062b1ca41f65ee69dfa
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!-- **** Filing a Feature Request? Include these sections. **** -->
|
2
|
+
|
3
|
+
### Feature request
|
4
|
+
<!-- Provide a summary of the behavior. -->
|
5
|
+
|
6
|
+
### Motivation
|
7
|
+
|
8
|
+
<!-- What would you like to do with this feature? Can you provide
|
9
|
+
context or references to similar behavior in other libraries? -->
|
10
|
+
|
11
|
+
<!-- **** Filing a Bug Report? Include these sections: **** -->
|
12
|
+
|
13
|
+
### Steps to reproduce
|
14
|
+
<!-- Provide a series of steps or, better yet, a link to a repo to demonstrate the bug you've identified. -->
|
15
|
+
|
16
|
+
### Expected behavior
|
17
|
+
<!-- Tell us what should happen -->
|
18
|
+
|
19
|
+
### Actual behavior
|
20
|
+
<!-- Tell us what happens instead -->
|
21
|
+
|
22
|
+
### System configuration
|
23
|
+
**Rails version**:
|
24
|
+
|
25
|
+
**Ruby version**:
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<!-- See https://github.com/github/actionview-component/blob/master/CONTRIBUTING.md#submitting-a-pull-request -->
|
2
|
+
|
3
|
+
### Summary
|
4
|
+
|
5
|
+
<!-- Provide a general description of the code changes in your pull
|
6
|
+
request... were there any bugs you had fixed? If so, mention them. If
|
7
|
+
these bugs have open GitHub issues, be sure to tag them here as well,
|
8
|
+
to keep the conversation linked together. -->
|
9
|
+
|
10
|
+
### Other Information
|
11
|
+
|
12
|
+
<!-- If there's anything else that's important and relevant to your pull
|
13
|
+
request, mention that information here. This could include
|
14
|
+
benchmarks, or other information.
|
15
|
+
|
16
|
+
If you are updating any of the CHANGELOG files or are asked to update the
|
17
|
+
CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file. -->
|
@@ -8,7 +8,7 @@ jobs:
|
|
8
8
|
strategy:
|
9
9
|
matrix:
|
10
10
|
rails_version: [5.0.0, 5.2.3, 6.0.0, master]
|
11
|
-
ruby_version: [2.
|
11
|
+
ruby_version: [2.5.x, 2.6.x, 2.7.x]
|
12
12
|
exclude:
|
13
13
|
- rails_version: master
|
14
14
|
ruby_version: 2.4.x
|
@@ -22,7 +22,7 @@ jobs:
|
|
22
22
|
ruby-version: ${{ matrix.ruby_version }}
|
23
23
|
- name: Build and test with Rake
|
24
24
|
run: |
|
25
|
-
gem install bundler:1.
|
25
|
+
gem install bundler:1.17.3
|
26
26
|
bundle update
|
27
27
|
bundle install --jobs 4 --retry 3
|
28
28
|
bundle exec rake
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,65 @@
|
|
1
|
+
# v1.9.0
|
2
|
+
|
3
|
+
* Remove initializer requirement for Ruby 2.7+
|
4
|
+
|
5
|
+
*Dylan Clark*
|
6
|
+
|
7
|
+
# v1.8.1
|
8
|
+
|
9
|
+
* Run validation checks before calling `#render?`.
|
10
|
+
|
11
|
+
*Ash Wilson*
|
12
|
+
|
13
|
+
# v1.8.0
|
14
|
+
|
15
|
+
* Remove the unneeded ComponentExamplesController and simplify preview rendering.
|
16
|
+
|
17
|
+
*Jon Palmer*
|
18
|
+
|
19
|
+
* Add `#render?` hook to easily allow components to be no-ops.
|
20
|
+
|
21
|
+
*Kyle Fox*
|
22
|
+
|
23
|
+
* Don't assume ApplicationController exists.
|
24
|
+
|
25
|
+
*Jon Palmer*
|
26
|
+
|
27
|
+
* Allow some additional checks to overrided render?
|
28
|
+
|
29
|
+
*Sergey Malykh*
|
30
|
+
|
31
|
+
* Fix generator placing namespaced components in the root directory.
|
32
|
+
|
33
|
+
*Asger Behncke Jacobsen*
|
34
|
+
|
35
|
+
* Fix cache test.
|
36
|
+
|
37
|
+
*Sergey Malykh*
|
38
|
+
|
39
|
+
# v1.7.0
|
40
|
+
|
41
|
+
* Simplify validation of templates and compilation.
|
42
|
+
|
43
|
+
*Jon Palmer*
|
44
|
+
|
45
|
+
* Add support for multiple content areas.
|
46
|
+
|
47
|
+
*Jon Palmer*
|
48
|
+
|
49
|
+
# v1.6.2
|
50
|
+
|
51
|
+
* Fix Uninitialized Constant error.
|
52
|
+
|
53
|
+
*Jon Palmer*
|
54
|
+
|
55
|
+
* Add basic github issue and PR templates.
|
56
|
+
|
57
|
+
*Dylan Clark*
|
58
|
+
|
59
|
+
* Update readme phrasing around previews.
|
60
|
+
|
61
|
+
*Justin Coyne*
|
62
|
+
|
1
63
|
# v1.6.1
|
2
64
|
|
3
65
|
* Allow Previews to have no layout.
|
data/CONTRIBUTING.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# Contributing
|
2
2
|
|
3
3
|
[fork]: https://github.com/github/actionview-component/fork
|
4
4
|
[pr]: https://github.com/github/actionview-component/compare
|
@@ -15,9 +15,11 @@ Please note that this project is released with a [Contributor Code of Conduct][c
|
|
15
15
|
|
16
16
|
0. [Fork][fork] and clone the repository
|
17
17
|
0. Configure and install the dependencies: `bundle`
|
18
|
-
0. Make sure the tests pass on your machine: `rake`
|
18
|
+
0. Make sure the tests pass on your machine: `bundle exec rake`
|
19
19
|
0. Create a new branch: `git checkout -b my-branch-name`
|
20
20
|
0. Make your change, add tests, and make sure the tests still pass
|
21
|
+
0. Add an entry to the top of `CHANGELOG.md` for your changes
|
22
|
+
0. If it's your first time contributing, add yourself to the contributors at the bottom of `README.md`
|
21
23
|
0. Push to your fork and [submit a pull request][pr]
|
22
24
|
0. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
23
25
|
|
@@ -32,24 +34,13 @@ Here are a few things you can do that will increase the likelihood of your pull
|
|
32
34
|
If you are the current maintainer of this gem:
|
33
35
|
|
34
36
|
1. Create a branch for the release: `git checkout -b release-vxx.xx.xx`
|
35
|
-
1. Bump gem version in `lib/action_view/component/version.rb`.
|
37
|
+
1. Bump gem version in `lib/action_view/component/version.rb`. Try to adhere to SemVer.
|
36
38
|
1. Add version heading/entries to `CHANGELOG.md`.
|
37
39
|
1. Make sure your local dependencies are up to date: `bundle`
|
38
40
|
1. Ensure that tests are green: `bundle exec rake`
|
39
|
-
1. Build a test gem `GEM_VERSION=$(git describe --tags 2>/dev/null | sed 's/-/./g' | sed 's/v//') gem build actionview-component.gemspec`
|
40
|
-
1. Test the test gem:
|
41
|
-
1. Bump the Gemfile and Gemfile.lock versions for an app which relies on this gem
|
42
|
-
1. Install the new gem locally
|
43
|
-
1. Test behavior locally, branch deploy, whatever needs to happen
|
44
41
|
1. Make a PR to github/actionview-component.
|
45
42
|
1. Build a local gem: `gem build actionview-component.gemspec`
|
46
43
|
1. Merge github/actionview-component PR
|
47
44
|
1. Tag and push: `git tag vx.xx.xx; git push --tags`
|
48
45
|
1. Create a GitHub release with the pushed tag (https://github.com/github/actionview-component/releases/new) and populate it with a list of the commits from `git log --pretty=format:"- %s" --reverse refs/tags/[OLD TAG]...refs/tags/[NEW TAG]`
|
49
46
|
1. Push to rubygems.org -- `gem push actionview-component-VERSION.gem`
|
50
|
-
|
51
|
-
## Resources
|
52
|
-
|
53
|
-
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
54
|
-
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
55
|
-
- [GitHub Help](https://help.github.com)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -15,7 +15,7 @@ As the goal of this gem is to be upstreamed into Rails, it is designed to integr
|
|
15
15
|
|
16
16
|
## Compatibility
|
17
17
|
|
18
|
-
`actionview-component` is tested for compatibility with combinations of Ruby `2.
|
18
|
+
`actionview-component` is tested for compatibility with combinations of Ruby `2.5`/`2.6` and Rails `5.0.0`/`5.2.3`/`6.0.0`/`6.1.0.alpha`.
|
19
19
|
|
20
20
|
## Installation
|
21
21
|
Add this line to your application's Gemfile:
|
@@ -199,6 +199,224 @@ An error will be raised:
|
|
199
199
|
|
200
200
|
`ActiveModel::ValidationError: Validation failed: Title can't be blank`
|
201
201
|
|
202
|
+
#### Content Areas
|
203
|
+
|
204
|
+
A component can declare additional content areas to be rendered in the component. For example:
|
205
|
+
|
206
|
+
`app/components/modal_component.rb`:
|
207
|
+
```ruby
|
208
|
+
class ModalComponent < ActionView::Component::Base
|
209
|
+
validates :user, :header, :body, presence: true
|
210
|
+
|
211
|
+
with_content_areas :header, :body
|
212
|
+
|
213
|
+
def initialize(user:)
|
214
|
+
@user = user
|
215
|
+
end
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
`app/components/modal_component.html.erb`:
|
220
|
+
```erb
|
221
|
+
<div class="modal">
|
222
|
+
<div class="header"><%= header %></div>
|
223
|
+
<div class="body"><%= body %>"></div>
|
224
|
+
</div>
|
225
|
+
```
|
226
|
+
|
227
|
+
We can render it in a view as:
|
228
|
+
|
229
|
+
```erb
|
230
|
+
<%= render(ModalComponent, user: {name: 'Jane'}) do |component| %>
|
231
|
+
<% component.with(:header) do %>
|
232
|
+
Hello <%= user[:name] %>
|
233
|
+
<% end %>
|
234
|
+
<% component.with(:body) do %>
|
235
|
+
<p>Have a great day.</p>
|
236
|
+
<% end %>
|
237
|
+
<% end %>
|
238
|
+
```
|
239
|
+
|
240
|
+
Which returns:
|
241
|
+
|
242
|
+
```html
|
243
|
+
<div class="modal">
|
244
|
+
<div class="header">Hello Jane</div>
|
245
|
+
<div class="body"><p>Have a great day.</p></div>
|
246
|
+
</div>
|
247
|
+
```
|
248
|
+
|
249
|
+
Content for content areas can be passed as arguments to the render method or as named blocks passed to the `with` method.
|
250
|
+
This allows a few different combinations of ways to render the component:
|
251
|
+
|
252
|
+
##### Required render argument optionally overridden or wrapped by a named block
|
253
|
+
|
254
|
+
`app/components/modal_component.rb`:
|
255
|
+
```ruby
|
256
|
+
class ModalComponent < ActionView::Component::Base
|
257
|
+
validates :header, :body, presence: true
|
258
|
+
|
259
|
+
with_content_areas :header, :body
|
260
|
+
|
261
|
+
def initialize(header:)
|
262
|
+
@header = header
|
263
|
+
end
|
264
|
+
end
|
265
|
+
```
|
266
|
+
|
267
|
+
```erb
|
268
|
+
<%= render(ModalComponent, header: "Hi!") do |component| %>
|
269
|
+
<% help_enabled? && component.with(:header) do %>
|
270
|
+
<span class="help_icon"><%= component.header %></span>
|
271
|
+
<% end %>
|
272
|
+
<% component.with(:body) do %>
|
273
|
+
<p>Have a great day.</p>
|
274
|
+
<% end %>
|
275
|
+
<% end %>
|
276
|
+
```
|
277
|
+
|
278
|
+
##### Required argument passed by render argument or by named block
|
279
|
+
|
280
|
+
`app/components/modal_component.rb`:
|
281
|
+
```ruby
|
282
|
+
class ModalComponent < ActionView::Component::Base
|
283
|
+
validates :header, :body, presence: true
|
284
|
+
|
285
|
+
with_content_areas :header, :body
|
286
|
+
|
287
|
+
def initialize(header: nil)
|
288
|
+
@header = header
|
289
|
+
end
|
290
|
+
end
|
291
|
+
```
|
292
|
+
|
293
|
+
`app/views/render_arg.html.erb`:
|
294
|
+
```erb
|
295
|
+
<%= render(ModalComponent, header: "Hi!") do |component| %>
|
296
|
+
<% component.with(:body) do %>
|
297
|
+
<p>Have a great day.</p>
|
298
|
+
<% end %>
|
299
|
+
<% end %>
|
300
|
+
```
|
301
|
+
|
302
|
+
`app/views/with_block.html.erb`:
|
303
|
+
```erb
|
304
|
+
<%= render(ModalComponent) do |component| %>
|
305
|
+
<% component.with(:header) do %>
|
306
|
+
<span class="help_icon">Hello</span>
|
307
|
+
<% end %>
|
308
|
+
<% component.with(:body) do %>
|
309
|
+
<p>Have a great day.</p>
|
310
|
+
<% end %>
|
311
|
+
<% end %>
|
312
|
+
```
|
313
|
+
|
314
|
+
##### Optional argument passed by render argument, by named block, or neither
|
315
|
+
|
316
|
+
`app/components/modal_component.rb`:
|
317
|
+
```ruby
|
318
|
+
class ModalComponent < ActionView::Component::Base
|
319
|
+
validates :body, presence: true
|
320
|
+
|
321
|
+
with_content_areas :header, :body
|
322
|
+
|
323
|
+
def initialize(header: nil)
|
324
|
+
@header = header
|
325
|
+
end
|
326
|
+
end
|
327
|
+
```
|
328
|
+
|
329
|
+
`app/components/modal_component.html.erb`:
|
330
|
+
```erb
|
331
|
+
<div class="modal">
|
332
|
+
<% if header %>
|
333
|
+
<div class="header"><%= header %></div>
|
334
|
+
<% end %>
|
335
|
+
<div class="body"><%= body %>"></div>
|
336
|
+
</div>
|
337
|
+
```
|
338
|
+
|
339
|
+
`app/views/render_arg.html.erb`:
|
340
|
+
```erb
|
341
|
+
<%= render(ModalComponent, header: "Hi!") do |component| %>
|
342
|
+
<% component.with(:body) do %>
|
343
|
+
<p>Have a great day.</p>
|
344
|
+
<% end %>
|
345
|
+
<% end %>
|
346
|
+
```
|
347
|
+
|
348
|
+
`app/views/with_block.html.erb`:
|
349
|
+
```erb
|
350
|
+
<%= render(ModalComponent) do |component| %>
|
351
|
+
<% component.with(:header) do %>
|
352
|
+
<span class="help_icon">Hello</span>
|
353
|
+
<% end %>
|
354
|
+
<% component.with(:body) do %>
|
355
|
+
<p>Have a great day.</p>
|
356
|
+
<% end %>
|
357
|
+
<% end %>
|
358
|
+
```
|
359
|
+
|
360
|
+
`app/views/no_header.html.erb`:
|
361
|
+
```erb
|
362
|
+
<%= render(ModalComponent) do |component| %>
|
363
|
+
<% component.with(:body) do %>
|
364
|
+
<p>Have a great day.</p>
|
365
|
+
<% end %>
|
366
|
+
<% end %>
|
367
|
+
```
|
368
|
+
|
369
|
+
### Conditional Rendering
|
370
|
+
|
371
|
+
Components can implement a `#render?` method which indicates if they should be rendered, or not at all.
|
372
|
+
|
373
|
+
For example, you might have a component that displays a "Please confirm your email address" banner to users who haven't confirmed their email address. The logic for rendering the banner would need to go in either the component template:
|
374
|
+
|
375
|
+
```
|
376
|
+
<!-- app/components/confirm_email_component.html.erb -->
|
377
|
+
<% if user.requires_confirmation? %>
|
378
|
+
<div class="alert">
|
379
|
+
Please confirm your email address.
|
380
|
+
</div>
|
381
|
+
<% end %>
|
382
|
+
```
|
383
|
+
|
384
|
+
or the view that renders the component:
|
385
|
+
|
386
|
+
```erb
|
387
|
+
<!-- app/views/_banners.html.erb -->
|
388
|
+
<% if current_user.requires_confirmation? %>
|
389
|
+
<%= render(ConfirmEmailComponent, user: current_user) %>
|
390
|
+
<% end %>
|
391
|
+
```
|
392
|
+
|
393
|
+
The `#render?` hook allows you to move this logic into the Ruby class, leaving your views more readable and declarative in style:
|
394
|
+
|
395
|
+
```ruby
|
396
|
+
# app/components/confirm_email_component.rb
|
397
|
+
class ConfirmEmailComponent < ApplicationComponent
|
398
|
+
def initialize(user:)
|
399
|
+
@user = user
|
400
|
+
end
|
401
|
+
|
402
|
+
def render?
|
403
|
+
@user.requires_confirmation?
|
404
|
+
end
|
405
|
+
end
|
406
|
+
```
|
407
|
+
|
408
|
+
```
|
409
|
+
<!-- app/components/confirm_email_component.html.erb -->
|
410
|
+
<div class="banner">
|
411
|
+
Please confirm your email address.
|
412
|
+
</div>
|
413
|
+
```
|
414
|
+
|
415
|
+
```erb
|
416
|
+
<!-- app/views/_banners.html.erb -->
|
417
|
+
<%= render(ConfirmEmailComponent, user: current_user) %>
|
418
|
+
```
|
419
|
+
|
202
420
|
### Testing
|
203
421
|
|
204
422
|
Components are unit tested directly. The `render_inline` test helper wraps the result in `Nokogiri.HTML`, allowing us to test the component above as:
|
@@ -234,7 +452,7 @@ end
|
|
234
452
|
```
|
235
453
|
|
236
454
|
### Previewing Components
|
237
|
-
`ActionView::Component::Preview`
|
455
|
+
`ActionView::Component::Preview` provides a way to see how components look by visiting a special URL that renders them.
|
238
456
|
In the previous example, the preview class for `TestComponent` would be called `TestComponentPreview` and located in `test/components/previews/test_component_preview.rb`.
|
239
457
|
To see the preview of the component with a given title, implement a method that renders the component.
|
240
458
|
You can define as many examples as you want:
|
@@ -276,6 +494,16 @@ For example, if you want to use `lib/component_previews`, set the following in `
|
|
276
494
|
config.action_view_component.preview_path = "#{Rails.root}/lib/component_previews"
|
277
495
|
```
|
278
496
|
|
497
|
+
#### Configuring TestController
|
498
|
+
|
499
|
+
By default components tests and previews expect your Rails project to contain an `ApplicationController` class from which Controller classes inherit.
|
500
|
+
This can be configured using the `test_controller` option.
|
501
|
+
For example, if your controllers inherit from `BaseController`, set the following in `config/application.rb`:
|
502
|
+
|
503
|
+
```ruby
|
504
|
+
config.action_view_component.test_controller = "BaseController"
|
505
|
+
```
|
506
|
+
|
279
507
|
### Setting up RSpec
|
280
508
|
|
281
509
|
If you're using RSpec, you can configure component specs to have access to test helpers. Add the following to
|
@@ -300,6 +528,11 @@ To use component previews, set the following in `config/application.rb`:
|
|
300
528
|
config.action_view_component.preview_path = "#{Rails.root}/spec/components/previews"
|
301
529
|
```
|
302
530
|
|
531
|
+
### Initializer requirement
|
532
|
+
|
533
|
+
In Ruby 2.6.x and below, ActionView::Component requires the presence of an `initialize` method in each component.
|
534
|
+
However, `initialize` is no longer required for projects using 2.7.x and above.
|
535
|
+
|
303
536
|
## Frequently Asked Questions
|
304
537
|
|
305
538
|
### Can I use other templating languages besides ERB?
|
@@ -331,6 +564,35 @@ Inline templates have been removed (for now) due to concerns raised by [@soutaro
|
|
331
564
|
|
332
565
|
Bug reports and pull requests are welcome on GitHub at https://github.com/github/actionview-component. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
|
333
566
|
|
567
|
+
## Contributors
|
568
|
+
|
569
|
+
`actionview-component` is built by:
|
570
|
+
|
571
|
+
|<img src="https://avatars.githubusercontent.com/joelhawksley?s=256" alt="joelhawksley" width="128" />|<img src="https://avatars.githubusercontent.com/tenderlove?s=256" alt="tenderlove" width="128" />|<img src="https://avatars.githubusercontent.com/jonspalmer?s=256" alt="jonspalmer" width="128" />|<img src="https://avatars.githubusercontent.com/juanmanuelramallo?s=256" alt="juanmanuelramallo" width="128" />|<img src="https://avatars.githubusercontent.com/vinistock?s=256" alt="vinistock" width="128" />|
|
572
|
+
|:---:|:---:|:---:|:---:|:---:|
|
573
|
+
|@joelhawksley|@tenderlove|@jonspalmer|@juanmanuelramallo|@vinistock|
|
574
|
+
|Denver|Seattle|Boston||Toronto|
|
575
|
+
|
576
|
+
|<img src="https://avatars.githubusercontent.com/metade?s=256" alt="metade" width="128" />|<img src="https://avatars.githubusercontent.com/asgerb?s=256" alt="asgerb" width="128" />|<img src="https://avatars.githubusercontent.com/xronos-i-am?s=256" alt="xronos-i-am" width="128" />|<img src="https://avatars.githubusercontent.com/dylnclrk?s=256" alt="dylnclrk" width="128" />|<img src="https://avatars.githubusercontent.com/kaspermeyer?s=256" alt="kaspermeyer" width="128" />|
|
577
|
+
|:---:|:---:|:---:|:---:|:---:|
|
578
|
+
|@metade|@asgerb|@xronos-i-am|@dylnclrk|@kaspermeyer|
|
579
|
+
|London|Copenhagen|Russia, Kirov|Berkeley, CA|Denmark|
|
580
|
+
|
581
|
+
|<img src="https://avatars.githubusercontent.com/rdavid1099?s=256" alt="rdavid1099" width="128" />|<img src="https://avatars.githubusercontent.com/kylefox?s=256" alt="kylefox" width="128" />|<img src="https://avatars.githubusercontent.com/traels?s=256" alt="traels" width="128" />|<img src="https://avatars.githubusercontent.com/rainerborene?s=256" alt="rainerborene" width="128" />|<img src="https://avatars.githubusercontent.com/jcoyne?s=256" alt="jcoyne" width="128" />|
|
582
|
+
|:---:|:---:|:---:|:---:|:---:|
|
583
|
+
|@rdavid1099|@kylefox|@traels|@rainerborene|@jcoyne|
|
584
|
+
|Los Angeles|Edmonton|Odense, Denmark|Brazil|Minneapolis|
|
585
|
+
|
586
|
+
|<img src="https://avatars.githubusercontent.com/elia?s=256" alt="elia" width="128" />|<img src="https://avatars.githubusercontent.com/cesariouy?s=256" alt="cesariouy" width="128" />|<img src="https://avatars.githubusercontent.com/spdawson?s=256" alt="spdawson" width="128" />|<img src="https://avatars.githubusercontent.com/rmacklin?s=256" alt="rmacklin" width="128" />|<img src="https://avatars.githubusercontent.com/michaelem?s=256" alt="michaelem" width="128" />|
|
587
|
+
|:---:|:---:|:---:|:---:|:---:|
|
588
|
+
|@elia|@cesariouy|@spdawson|@rmacklin|@michaelem|
|
589
|
+
|Milan||United Kingdom||Berlin|
|
590
|
+
|
591
|
+
|<img src="https://avatars.githubusercontent.com/mellowfish?s=256" alt="mellowfish" width="128" />|<img src="https://avatars.githubusercontent.com/horacio?s=256" alt="horacio" width="128" />|<img src="https://avatars.githubusercontent.com/dukex?s=256" alt="dukex" width="128" />|<img src="https://avatars.githubusercontent.com/dark-panda?s=256" alt="dark-panda" width="128" />|<img src="https://avatars.githubusercontent.com/smashwilson?s=256" alt="smashwilson" width="128" />|
|
592
|
+
|:---:|:---:|:---:|:---:|:---:|
|
593
|
+
|@mellowfish|@horacio|@dukex|@dark-panda|@smashwilson|
|
594
|
+
|Spring Hill, TN|Buenos Aires|São Paulo||Gambrills, MD|
|
595
|
+
|
334
596
|
## License
|
335
597
|
|
336
598
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -11,6 +11,9 @@ module ActionView
|
|
11
11
|
|
12
12
|
delegate :form_authenticity_token, :protect_against_forgery?, to: :helpers
|
13
13
|
|
14
|
+
class_attribute :content_areas, default: []
|
15
|
+
self.content_areas = [] # default doesn't work until Rails 5.2
|
16
|
+
|
14
17
|
# Entrypoint for rendering components. Called by ActionView::Base#render.
|
15
18
|
#
|
16
19
|
# view_context: ActionView context from calling view
|
@@ -37,24 +40,32 @@ module ActionView
|
|
37
40
|
# <span title="greeting">Hello, world!</span>
|
38
41
|
#
|
39
42
|
def render_in(view_context, *args, &block)
|
40
|
-
self.class.compile
|
43
|
+
self.class.compile!
|
41
44
|
@view_context = view_context
|
42
45
|
@view_renderer ||= view_context.view_renderer
|
43
46
|
@lookup_context ||= view_context.lookup_context
|
44
47
|
@view_flow ||= view_context.view_flow
|
45
48
|
@virtual_path ||= virtual_path
|
46
49
|
@variant = @lookup_context.variants.first
|
50
|
+
|
47
51
|
old_current_template = @current_template
|
48
52
|
@current_template = self
|
49
53
|
|
50
|
-
@content = view_context.capture(&block) if block_given?
|
54
|
+
@content = view_context.capture(self, &block) if block_given?
|
55
|
+
|
51
56
|
validate!
|
52
57
|
|
58
|
+
return "" unless render?
|
59
|
+
|
53
60
|
send(self.class.call_method_name(@variant))
|
54
61
|
ensure
|
55
62
|
@current_template = old_current_template
|
56
63
|
end
|
57
64
|
|
65
|
+
def render?
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
58
69
|
def initialize(*); end
|
59
70
|
|
60
71
|
def render(options = {}, args = {}, &block)
|
@@ -87,6 +98,19 @@ module ActionView
|
|
87
98
|
@variant
|
88
99
|
end
|
89
100
|
|
101
|
+
def with(area, content = nil, &block)
|
102
|
+
unless content_areas.include?(area)
|
103
|
+
raise ArgumentError.new "Unknown content_area '#{area}' - expected one of '#{content_areas}'"
|
104
|
+
end
|
105
|
+
|
106
|
+
if block_given?
|
107
|
+
content = view_context.capture(&block)
|
108
|
+
end
|
109
|
+
|
110
|
+
instance_variable_set("@#{area}".to_sym, content)
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
90
114
|
private
|
91
115
|
|
92
116
|
def request
|
@@ -95,6 +119,12 @@ module ActionView
|
|
95
119
|
|
96
120
|
attr_reader :content, :view_context
|
97
121
|
|
122
|
+
# The controller used for testing components.
|
123
|
+
# Defaults to ApplicationController. This should be set early
|
124
|
+
# in the initialization process and should be set to a string.
|
125
|
+
mattr_accessor :test_controller
|
126
|
+
@@test_controller = "ApplicationController"
|
127
|
+
|
98
128
|
class << self
|
99
129
|
def inherited(child)
|
100
130
|
child.include Rails.application.routes.url_helpers unless child < Rails.application.routes.url_helpers
|
@@ -110,40 +140,36 @@ module ActionView
|
|
110
140
|
end
|
111
141
|
end
|
112
142
|
|
113
|
-
def has_initializer?
|
114
|
-
self.instance_method(:initialize).owner == self
|
115
|
-
end
|
116
|
-
|
117
143
|
def source_location
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
instance_method(:initialize).source_location[0]
|
128
|
-
end
|
129
|
-
|
130
|
-
def eager_load!
|
131
|
-
self.descendants.each do |descendant|
|
132
|
-
descendant.compile if descendant.has_initializer?
|
133
|
-
end
|
144
|
+
@source_location ||=
|
145
|
+
if const_source_location_supported?
|
146
|
+
const_source_location(self.name)[0]
|
147
|
+
else
|
148
|
+
# Require `#initialize` to be defined so that we can use `method#source_location`
|
149
|
+
# to look up the filename of the component.
|
150
|
+
initialize_method = instance_method(:initialize)
|
151
|
+
initialize_method.source_location[0] if initialize_method.owner == self
|
152
|
+
end
|
134
153
|
end
|
135
154
|
|
136
155
|
def compiled?
|
137
156
|
@compiled && ActionView::Base.cache_template_loading
|
138
157
|
end
|
139
158
|
|
159
|
+
def compile!
|
160
|
+
compile(validate: true)
|
161
|
+
end
|
162
|
+
|
140
163
|
# Compile templates to instance methods, assuming they haven't been compiled already.
|
141
164
|
# We could in theory do this on app boot, at least in production environments.
|
142
165
|
# Right now this just compiles the first time the component is rendered.
|
143
|
-
def compile
|
166
|
+
def compile(validate: false)
|
144
167
|
return if compiled?
|
145
168
|
|
146
|
-
|
169
|
+
if template_errors.present?
|
170
|
+
raise ActionView::Component::TemplateError.new(template_errors) if validate
|
171
|
+
return false
|
172
|
+
end
|
147
173
|
|
148
174
|
templates.each do |template|
|
149
175
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
@@ -170,10 +196,23 @@ module ActionView
|
|
170
196
|
source_location
|
171
197
|
end
|
172
198
|
|
199
|
+
def with_content_areas(*areas)
|
200
|
+
if areas.include?(:content)
|
201
|
+
raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
|
202
|
+
end
|
203
|
+
attr_reader *areas
|
204
|
+
self.content_areas = areas
|
205
|
+
end
|
206
|
+
|
173
207
|
private
|
174
208
|
|
209
|
+
def const_source_location_supported?
|
210
|
+
respond_to? :const_source_location # introduced in Ruby 2.7
|
211
|
+
end
|
212
|
+
|
175
213
|
def matching_views_in_source_location
|
176
|
-
|
214
|
+
return [] unless source_location
|
215
|
+
(Dir["#{source_location.chomp(File.extname(source_location))}.*{#{ActionView::Template.template_handler_extensions.join(',')}}"] - [source_location])
|
177
216
|
end
|
178
217
|
|
179
218
|
def templates
|
@@ -183,26 +222,39 @@ module ActionView
|
|
183
222
|
|
184
223
|
memo << {
|
185
224
|
path: path,
|
186
|
-
variant: pieces.second.split("+")
|
225
|
+
variant: pieces.second.split("+").second&.to_sym,
|
187
226
|
handler: pieces.last
|
188
227
|
}
|
189
228
|
end
|
190
229
|
end
|
191
230
|
|
192
|
-
def
|
193
|
-
|
194
|
-
|
195
|
-
|
231
|
+
def template_errors
|
232
|
+
@template_errors ||=
|
233
|
+
begin
|
234
|
+
errors = []
|
235
|
+
if source_location.nil? && !const_source_location_supported?
|
236
|
+
# Require `#initialize` to be defined so that we can use `method#source_location`
|
237
|
+
# to look up the filename of the component.
|
238
|
+
errors << "#{self} must implement #initialize."
|
239
|
+
end
|
196
240
|
|
197
|
-
|
198
|
-
raise StandardError.new("More than one template found for #{self}. There can only be one default template file per component.")
|
199
|
-
end
|
241
|
+
errors << "Could not find a template file for #{self}." if templates.empty?
|
200
242
|
|
201
|
-
|
202
|
-
|
243
|
+
if templates.count { |template| template[:variant].nil? } > 1
|
244
|
+
errors << "More than one template found for #{self}. There can only be one default template file per component."
|
245
|
+
end
|
203
246
|
|
204
|
-
|
205
|
-
|
247
|
+
invalid_variants = templates
|
248
|
+
.group_by { |template| template[:variant] }
|
249
|
+
.map { |variant, grouped| variant if grouped.length > 1 }
|
250
|
+
.compact
|
251
|
+
.sort
|
252
|
+
|
253
|
+
unless invalid_variants.empty?
|
254
|
+
errors << "More than one template found for #{'variant'.pluralize(invalid_variants.count)} #{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{self}. There can only be one template file per variant."
|
255
|
+
end
|
256
|
+
errors
|
257
|
+
end
|
206
258
|
end
|
207
259
|
|
208
260
|
def compiled_template(file_path)
|
@@ -6,10 +6,9 @@ module ActionView
|
|
6
6
|
module Component # :nodoc:
|
7
7
|
class Preview
|
8
8
|
extend ActiveSupport::DescendantsTracker
|
9
|
-
include ActionView::Component::TestHelpers
|
10
9
|
|
11
|
-
def render(component,
|
12
|
-
|
10
|
+
def render(component, **args, &block)
|
11
|
+
{ component: component, args: args, block: block }
|
13
12
|
end
|
14
13
|
|
15
14
|
class << self
|
@@ -19,21 +18,14 @@ module ActionView
|
|
19
18
|
descendants
|
20
19
|
end
|
21
20
|
|
22
|
-
# Returns the
|
23
|
-
def
|
24
|
-
|
25
|
-
if layout.nil?
|
26
|
-
layout = @layout.nil? ? "layouts/application" : @layout
|
27
|
-
end
|
28
|
-
|
29
|
-
Rails::ComponentExamplesController.render(template: "examples/show",
|
30
|
-
layout: layout,
|
31
|
-
assigns: { example: example_html })
|
21
|
+
# Returns the arguments for rendering of the component in its layout
|
22
|
+
def render_args(example)
|
23
|
+
new.public_send(example).merge(layout: @layout)
|
32
24
|
end
|
33
25
|
|
34
26
|
# Returns the component object class associated to the preview.
|
35
27
|
def component
|
36
|
-
|
28
|
+
name.chomp("Preview").constantize
|
37
29
|
end
|
38
30
|
|
39
31
|
# Returns all of the available examples for the component preview.
|
@@ -58,7 +50,7 @@ module ActionView
|
|
58
50
|
|
59
51
|
# Returns the underscored name of the component preview without the suffix.
|
60
52
|
def preview_name
|
61
|
-
name.
|
53
|
+
name.chomp("Preview").underscore
|
62
54
|
end
|
63
55
|
|
64
56
|
# Setter for layout name.
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails"
|
4
|
+
require "action_view/component"
|
4
5
|
|
5
6
|
module ActionView
|
6
7
|
module Component
|
@@ -23,9 +24,7 @@ module ActionView
|
|
23
24
|
|
24
25
|
initializer "action_view_component.set_autoload_paths" do |app|
|
25
26
|
require "railties/lib/rails/components_controller"
|
26
|
-
require "railties/lib/rails/component_examples_controller"
|
27
27
|
|
28
|
-
app.config.eager_load_namespaces << ActionView::Component::Base
|
29
28
|
options = app.config.action_view_component
|
30
29
|
|
31
30
|
if options.show_previews && options.preview_path
|
@@ -33,6 +32,12 @@ module ActionView
|
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
35
|
+
initializer "action_view_component.eager_load_actions" do
|
36
|
+
ActiveSupport.on_load(:after_initialize) do
|
37
|
+
ActionView::Component::Base.descendants.each(&:compile)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
36
41
|
initializer "action_view_component.compile_config_methods" do
|
37
42
|
ActiveSupport.on_load(:action_view_component) do
|
38
43
|
config.compile_methods! if config.respond_to?(:compile_methods!)
|
@@ -8,7 +8,7 @@ module ActionView
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def controller
|
11
|
-
@controller ||=
|
11
|
+
@controller ||= Base.test_controller.constantize.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
|
12
12
|
end
|
13
13
|
|
14
14
|
def request
|
@@ -10,11 +10,11 @@ module Rails
|
|
10
10
|
check_class_collision suffix: "Component"
|
11
11
|
|
12
12
|
def create_component_file
|
13
|
-
template "component.rb", File.join("app/components",
|
13
|
+
template "component.rb", File.join("app/components", class_path, "#{file_name}_component.rb")
|
14
14
|
end
|
15
15
|
|
16
16
|
def create_template_file
|
17
|
-
template "component.html.erb", File.join("app/components",
|
17
|
+
template "component.html.erb", File.join("app/components", class_path, "#{file_name}_component.html.erb")
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
@@ -6,7 +6,7 @@ module Rspec
|
|
6
6
|
source_root File.expand_path("templates", __dir__)
|
7
7
|
|
8
8
|
def create_test_file
|
9
|
-
template "component_spec.rb", File.join("spec/components", "#{file_name}_component_spec.rb")
|
9
|
+
template "component_spec.rb", File.join("spec/components", class_path, "#{file_name}_component_spec.rb")
|
10
10
|
end
|
11
11
|
|
12
12
|
private
|
@@ -7,7 +7,7 @@ module TestUnit
|
|
7
7
|
check_class_collision suffix: "ComponentTest"
|
8
8
|
|
9
9
|
def create_test_file
|
10
|
-
template "component_test.rb", File.join("test/components", "#{file_name}_component_test.rb")
|
10
|
+
template "component_test.rb", File.join("test/components", class_path, "#{file_name}_component_test.rb")
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
data/lib/railties/lib/rails.rb
CHANGED
@@ -4,6 +4,7 @@ require "rails/application_controller"
|
|
4
4
|
|
5
5
|
class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
|
6
6
|
prepend_view_path File.expand_path("templates/rails", __dir__)
|
7
|
+
prepend_view_path "#{Rails.root}/app/views/" if defined?(Rails.root)
|
7
8
|
|
8
9
|
around_action :set_locale, only: :previews
|
9
10
|
before_action :find_preview, only: :previews
|
@@ -25,7 +26,10 @@ class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
|
|
25
26
|
render template: "components/previews"
|
26
27
|
else
|
27
28
|
@example_name = File.basename(params[:path])
|
28
|
-
|
29
|
+
@render_args = @preview.render_args(@example_name)
|
30
|
+
layout = @render_args[:layout]
|
31
|
+
opts = layout.nil? ? {} : { layout: layout }
|
32
|
+
render template: "components/preview", **opts
|
29
33
|
end
|
30
34
|
end
|
31
35
|
|
@@ -1 +1 @@
|
|
1
|
-
<%=
|
1
|
+
<%= render(@render_args[:component], **@render_args[:args], &@render_args[:block])%>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionview-component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -129,6 +129,8 @@ executables: []
|
|
129
129
|
extensions: []
|
130
130
|
extra_rdoc_files: []
|
131
131
|
files:
|
132
|
+
- ".github/ISSUE_TEMPLATE"
|
133
|
+
- ".github/PULL_REQUEST_TEMPLATE"
|
132
134
|
- ".github/workflows/ruby_on_rails.yml"
|
133
135
|
- ".gitignore"
|
134
136
|
- ".rubocop.yml"
|
@@ -148,6 +150,7 @@ files:
|
|
148
150
|
- lib/action_view/component/previewable.rb
|
149
151
|
- lib/action_view/component/railtie.rb
|
150
152
|
- lib/action_view/component/render_monkey_patch.rb
|
153
|
+
- lib/action_view/component/template_error.rb
|
151
154
|
- lib/action_view/component/test_case.rb
|
152
155
|
- lib/action_view/component/test_helpers.rb
|
153
156
|
- lib/action_view/component/version.rb
|
@@ -160,12 +163,10 @@ files:
|
|
160
163
|
- lib/rails/generators/test_unit/component_generator.rb
|
161
164
|
- lib/rails/generators/test_unit/templates/component_test.rb.tt
|
162
165
|
- lib/railties/lib/rails.rb
|
163
|
-
- lib/railties/lib/rails/component_examples_controller.rb
|
164
166
|
- lib/railties/lib/rails/components_controller.rb
|
165
167
|
- lib/railties/lib/rails/templates/rails/components/index.html.erb
|
166
168
|
- lib/railties/lib/rails/templates/rails/components/preview.html.erb
|
167
169
|
- lib/railties/lib/rails/templates/rails/components/previews.html.erb
|
168
|
-
- lib/railties/lib/rails/templates/rails/examples/show.html.erb
|
169
170
|
- script/bootstrap
|
170
171
|
- script/console
|
171
172
|
- script/install
|
@@ -1,9 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "rails/application_controller"
|
4
|
-
load "config/application.rb" unless Rails.root
|
5
|
-
|
6
|
-
class Rails::ComponentExamplesController < ActionController::Base # :nodoc:
|
7
|
-
prepend_view_path File.expand_path("templates/rails", __dir__)
|
8
|
-
append_view_path Rails.root.join("app/views")
|
9
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
<%= raw @example %>
|