view_component 2.18.1 → 2.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +130 -78
- data/README.md +160 -39
- data/lib/rails/generators/test_unit/templates/component_test.rb.tt +1 -1
- data/lib/view_component.rb +1 -0
- data/lib/view_component/base.rb +17 -185
- data/lib/view_component/collection.rb +2 -0
- data/lib/view_component/compiler.rb +214 -0
- data/lib/view_component/engine.rb +10 -5
- data/lib/view_component/preview.rb +4 -15
- data/lib/view_component/previewable.rb +10 -0
- data/lib/view_component/test_helpers.rb +4 -0
- data/lib/view_component/version.rb +2 -2
- metadata +20 -5
data/README.md
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# ViewComponent
|
2
|
+
|
2
3
|
ViewComponent is a framework for building view components that are reusable, testable & encapsulated, in Ruby on Rails.
|
3
4
|
|
4
5
|
## Design philosophy
|
6
|
+
|
5
7
|
ViewComponent is designed to integrate as seamlessly as possible [with Rails](https://rubyonrails.org/doctrine/), with the [least surprise](https://www.artima.com/intv/ruby4.html).
|
6
8
|
|
7
9
|
## Compatibility
|
10
|
+
|
8
11
|
ViewComponent is [supported natively](https://edgeguides.rubyonrails.org/layouts_and_rendering.html#rendering-objects) in Rails 6.1, and compatible with Rails 5.0+ via an included [monkey patch](https://github.com/github/view_component/blob/master/lib/view_component/render_monkey_patch.rb).
|
9
12
|
|
10
13
|
ViewComponent is tested for compatibility [with combinations of](https://github.com/github/view_component/blob/22e3d4ccce70d8f32c7375e5a5ccc3f70b22a703/.github/workflows/ruby_on_rails.yml#L10-L11) Ruby 2.4+ and Rails 5+.
|
@@ -14,13 +17,7 @@ ViewComponent is tested for compatibility [with combinations of](https://github.
|
|
14
17
|
In `Gemfile`, add:
|
15
18
|
|
16
19
|
```ruby
|
17
|
-
gem "view_component"
|
18
|
-
```
|
19
|
-
|
20
|
-
In `config/application.rb`, add:
|
21
|
-
|
22
|
-
```bash
|
23
|
-
require "view_component/engine"
|
20
|
+
gem "view_component", require: "view_component/engine"
|
24
21
|
```
|
25
22
|
|
26
23
|
## Guide
|
@@ -47,6 +44,10 @@ Traditional Rails views have an implicit interface, making it hard to reason abo
|
|
47
44
|
|
48
45
|
ViewComponents use a standard Ruby initializer that clearly defines what is needed to render, making them easier (and safer) to reuse than partials.
|
49
46
|
|
47
|
+
#### Performance
|
48
|
+
|
49
|
+
Based on our [benchmarks](performance/benchmark.rb), ViewComponents are ~10x faster than partials.
|
50
|
+
|
50
51
|
#### Standards
|
51
52
|
|
52
53
|
Views often fail basic Ruby code quality standards: long methods, deep conditional nesting, and mystery guests abound.
|
@@ -63,6 +64,8 @@ Component names end in -`Component`.
|
|
63
64
|
|
64
65
|
Component module names are plural, as for controllers and jobs: `Users::AvatarComponent`
|
65
66
|
|
67
|
+
Name components for what they render, not what they accept. (`AvatarComponent` instead of `UserComponent`)
|
68
|
+
|
66
69
|
#### Quick start
|
67
70
|
|
68
71
|
Use the component generator to create a new ViewComponent.
|
@@ -90,6 +93,7 @@ bin/rails generate component Example title content --template-engine slim
|
|
90
93
|
A ViewComponent is a Ruby file and corresponding template file with the same base name:
|
91
94
|
|
92
95
|
`app/components/test_component.rb`:
|
96
|
+
|
93
97
|
```ruby
|
94
98
|
class TestComponent < ViewComponent::Base
|
95
99
|
def initialize(title:)
|
@@ -99,6 +103,7 @@ end
|
|
99
103
|
```
|
100
104
|
|
101
105
|
`app/components/test_component.html.erb`:
|
106
|
+
|
102
107
|
```erb
|
103
108
|
<span title="<%= @title %>"><%= content %></span>
|
104
109
|
```
|
@@ -124,6 +129,7 @@ Content passed to a ViewComponent as a block is captured and assigned to the `co
|
|
124
129
|
ViewComponents can declare additional content areas. For example:
|
125
130
|
|
126
131
|
`app/components/modal_component.rb`:
|
132
|
+
|
127
133
|
```ruby
|
128
134
|
class ModalComponent < ViewComponent::Base
|
129
135
|
with_content_areas :header, :body
|
@@ -131,6 +137,7 @@ end
|
|
131
137
|
```
|
132
138
|
|
133
139
|
`app/components/modal_component.html.erb`:
|
140
|
+
|
134
141
|
```erb
|
135
142
|
<div class="modal">
|
136
143
|
<div class="header"><%= header %></div>
|
@@ -164,19 +171,17 @@ Returning:
|
|
164
171
|
|
165
172
|
_Slots are currently under development as a successor to Content Areas. The Slot APIs should be considered unfinished and subject to breaking changes in non-major releases of ViewComponent._
|
166
173
|
|
167
|
-
Slots enable multiple blocks of content to be passed to a single ViewComponent.
|
168
|
-
|
169
|
-
Slots exist in two forms: normal slots and collection slots.
|
174
|
+
Slots enable multiple blocks of content to be passed to a single ViewComponent, reducing the need for sub-components (e.g. ModalHeader, ModalBody).
|
170
175
|
|
171
|
-
|
176
|
+
By default, slots can be rendered once per component. They provide an accessor with the name of the slot (`#header`) that returns an instance of `ViewComponent::Slot`, etc.
|
172
177
|
|
173
|
-
|
178
|
+
Slots declared with `collection: true` can be rendered multiple times. They provide an accessor with the pluralized name of the slot (`#rows`), which is an Array of `ViewComponent::Slot` instances.
|
174
179
|
|
175
|
-
To learn more about the design of the Slots API, see https://github.com/github/view_component/pull/348.
|
180
|
+
To learn more about the design of the Slots API, see [#348](https://github.com/github/view_component/pull/348) and [#325](https://github.com/github/view_component/discussions/325).
|
176
181
|
|
177
182
|
##### Defining Slots
|
178
183
|
|
179
|
-
Slots are defined by
|
184
|
+
Slots are defined by `with_slot`:
|
180
185
|
|
181
186
|
`with_slot :header`
|
182
187
|
|
@@ -184,15 +189,16 @@ To define a collection slot, add `collection: true`:
|
|
184
189
|
|
185
190
|
`with_slot :row, collection: true`
|
186
191
|
|
187
|
-
To define a slot with a custom class, pass `class_name`:
|
192
|
+
To define a slot with a custom Ruby class, pass `class_name`:
|
188
193
|
|
189
|
-
`with_slot :body, class_name: 'BodySlot`
|
194
|
+
`with_slot :body, class_name: 'BodySlot'`
|
190
195
|
|
191
|
-
Slot classes
|
196
|
+
_Note: Slot classes must be subclasses of `ViewComponent::Slot`._
|
192
197
|
|
193
198
|
##### Example ViewComponent with Slots
|
194
199
|
|
195
200
|
`# box_component.rb`
|
201
|
+
|
196
202
|
```ruby
|
197
203
|
class BoxComponent < ViewComponent::Base
|
198
204
|
include ViewComponent::Slotable
|
@@ -202,12 +208,12 @@ class BoxComponent < ViewComponent::Base
|
|
202
208
|
with_slot :row, collection: true, class_name: "Row"
|
203
209
|
|
204
210
|
class Header < ViewComponent::Slot
|
205
|
-
def initialize(
|
206
|
-
@
|
211
|
+
def initialize(classes: "")
|
212
|
+
@classes = classes
|
207
213
|
end
|
208
214
|
|
209
|
-
def
|
210
|
-
"Box-header #{@
|
215
|
+
def classes
|
216
|
+
"Box-header #{@classes}"
|
211
217
|
end
|
212
218
|
end
|
213
219
|
|
@@ -237,10 +243,11 @@ end
|
|
237
243
|
```
|
238
244
|
|
239
245
|
`# box_component.html.erb`
|
246
|
+
|
240
247
|
```erb
|
241
248
|
<div class="Box">
|
242
249
|
<% if header %>
|
243
|
-
<div class="<%= header.
|
250
|
+
<div class="<%= header.classes %>">
|
244
251
|
<%= header.content %>
|
245
252
|
</div>
|
246
253
|
<% end %>
|
@@ -260,16 +267,17 @@ end
|
|
260
267
|
<% end %>
|
261
268
|
<% if footer %>
|
262
269
|
<div class="Box-footer">
|
263
|
-
<%= footer %>
|
270
|
+
<%= footer.content %>
|
264
271
|
</div>
|
265
272
|
<% end %>
|
266
273
|
</div>
|
267
274
|
```
|
268
275
|
|
269
276
|
`# index.html.erb`
|
277
|
+
|
270
278
|
```erb
|
271
279
|
<%= render(BoxComponent.new) do |component| %>
|
272
|
-
<% component.slot(:header,
|
280
|
+
<% component.slot(:header, classes: "my-class-name") do %>
|
273
281
|
This is my header!
|
274
282
|
<% end %>
|
275
283
|
<% component.slot(:body) do %>
|
@@ -292,6 +300,7 @@ end
|
|
292
300
|
ViewComponents can render without a template file, by defining a `call` method:
|
293
301
|
|
294
302
|
`app/components/inline_component.rb`:
|
303
|
+
|
295
304
|
```ruby
|
296
305
|
class InlineComponent < ViewComponent::Base
|
297
306
|
def call
|
@@ -318,6 +327,18 @@ class InlineVariantComponent < ViewComponent::Base
|
|
318
327
|
end
|
319
328
|
```
|
320
329
|
|
330
|
+
### Template Inheritance
|
331
|
+
|
332
|
+
Components that subclass another component inherit the parent component's
|
333
|
+
template if they don't define their own template.
|
334
|
+
|
335
|
+
```ruby
|
336
|
+
# If `my_link_component.html.erb` is not defined the component will fall back
|
337
|
+
# to `LinkComponent`s template
|
338
|
+
class MyLinkComponent < LinkComponent
|
339
|
+
end
|
340
|
+
```
|
341
|
+
|
321
342
|
### Sidecar Assets
|
322
343
|
|
323
344
|
ViewComponents supports two options for defining view files.
|
@@ -326,7 +347,7 @@ ViewComponents supports two options for defining view files.
|
|
326
347
|
|
327
348
|
The simplest option is to place the view next to the Ruby component:
|
328
349
|
|
329
|
-
```
|
350
|
+
```console
|
330
351
|
app/components
|
331
352
|
├── ...
|
332
353
|
├── test_component.rb
|
@@ -338,7 +359,7 @@ app/components
|
|
338
359
|
|
339
360
|
As an alternative, views and other assets can be placed in a sidecar directory with the same name as the component, which can be useful for organizing views alongside other assets like Javascript and CSS.
|
340
361
|
|
341
|
-
```
|
362
|
+
```console
|
342
363
|
app/components
|
343
364
|
├── ...
|
344
365
|
├── example_component.rb
|
@@ -347,12 +368,11 @@ app/components
|
|
347
368
|
| ├── example_component.html.erb
|
348
369
|
| └── example_component.js
|
349
370
|
├── ...
|
350
|
-
|
351
371
|
```
|
352
372
|
|
353
373
|
To generate a component with a sidecar directory, use the `--sidecar` flag:
|
354
374
|
|
355
|
-
```
|
375
|
+
```console
|
356
376
|
bin/rails generate component Example title content --sidecar
|
357
377
|
invoke test_unit
|
358
378
|
create test/components/example_component_test.rb
|
@@ -360,6 +380,32 @@ bin/rails generate component Example title content --sidecar
|
|
360
380
|
create app/components/example_component/example_component.html.erb
|
361
381
|
```
|
362
382
|
|
383
|
+
#### Component file inside Sidecar directory
|
384
|
+
|
385
|
+
It's also possible to place the Ruby component file inside the sidecar directory, grouping all related files in the same folder:
|
386
|
+
|
387
|
+
_Note: Avoid giving your containing folder the same name as your `.rb` file or there will be a conflict between Module and Class definitions_
|
388
|
+
|
389
|
+
```console
|
390
|
+
app/components
|
391
|
+
├── ...
|
392
|
+
├── example
|
393
|
+
| ├── component.rb
|
394
|
+
| ├── component.css
|
395
|
+
| ├── component.html.erb
|
396
|
+
| └── component.js
|
397
|
+
├── ...
|
398
|
+
|
399
|
+
```
|
400
|
+
|
401
|
+
The component can then be rendered using the folder name as a namespace:
|
402
|
+
|
403
|
+
```erb
|
404
|
+
<%= render(Example::Component.new(title: "my title")) do %>
|
405
|
+
Hello, World!
|
406
|
+
<% end %>
|
407
|
+
```
|
408
|
+
|
363
409
|
### Conditional Rendering
|
364
410
|
|
365
411
|
Components can implement a `#render?` method to be called after initialization to determine if the component should render.
|
@@ -367,7 +413,8 @@ Components can implement a `#render?` method to be called after initialization t
|
|
367
413
|
Traditionally, the logic for whether to render a view could go in either the component template:
|
368
414
|
|
369
415
|
`app/components/confirm_email_component.html.erb`
|
370
|
-
|
416
|
+
|
417
|
+
```erb
|
371
418
|
<% if user.requires_confirmation? %>
|
372
419
|
<div class="alert">Please confirm your email address.</div>
|
373
420
|
<% end %>
|
@@ -376,6 +423,7 @@ Traditionally, the logic for whether to render a view could go in either the com
|
|
376
423
|
or the view that renders the component:
|
377
424
|
|
378
425
|
`app/views/_banners.html.erb`
|
426
|
+
|
379
427
|
```erb
|
380
428
|
<% if current_user.requires_confirmation? %>
|
381
429
|
<%= render(ConfirmEmailComponent.new(user: current_user)) %>
|
@@ -385,6 +433,7 @@ or the view that renders the component:
|
|
385
433
|
Using the `#render?` hook simplifies the view:
|
386
434
|
|
387
435
|
`app/components/confirm_email_component.rb`
|
436
|
+
|
388
437
|
```ruby
|
389
438
|
class ConfirmEmailComponent < ViewComponent::Base
|
390
439
|
def initialize(user:)
|
@@ -398,13 +447,15 @@ end
|
|
398
447
|
```
|
399
448
|
|
400
449
|
`app/components/confirm_email_component.html.erb`
|
401
|
-
|
450
|
+
|
451
|
+
```erb
|
402
452
|
<div class="banner">
|
403
453
|
Please confirm your email address.
|
404
454
|
</div>
|
405
455
|
```
|
406
456
|
|
407
457
|
`app/views/_banners.html.erb`
|
458
|
+
|
408
459
|
```erb
|
409
460
|
<%= render(ConfirmEmailComponent.new(user: current_user)) %>
|
410
461
|
```
|
@@ -416,6 +467,7 @@ _To assert that a component has not been rendered, use `refute_component_rendere
|
|
416
467
|
Components can define a `before_render` method to be called before a component is rendered, when `helpers` is able to be used:
|
417
468
|
|
418
469
|
`app/components/confirm_email_component.rb`
|
470
|
+
|
419
471
|
```ruby
|
420
472
|
class MyComponent < ViewComponent::Base
|
421
473
|
def before_render
|
@@ -429,11 +481,13 @@ end
|
|
429
481
|
Use `with_collection` to render a ViewComponent with a collection:
|
430
482
|
|
431
483
|
`app/view/products/index.html.erb`
|
484
|
+
|
432
485
|
``` erb
|
433
486
|
<%= render(ProductComponent.with_collection(@products)) %>
|
434
487
|
```
|
435
488
|
|
436
489
|
`app/components/product_component.rb`
|
490
|
+
|
437
491
|
``` ruby
|
438
492
|
class ProductComponent < ViewComponent::Base
|
439
493
|
def initialize(product:)
|
@@ -449,6 +503,7 @@ end
|
|
449
503
|
Use `with_collection_parameter` to change the name of the collection parameter:
|
450
504
|
|
451
505
|
`app/components/product_component.rb`
|
506
|
+
|
452
507
|
``` ruby
|
453
508
|
class ProductComponent < ViewComponent::Base
|
454
509
|
with_collection_parameter :item
|
@@ -464,11 +519,13 @@ end
|
|
464
519
|
Additional arguments besides the collection are passed to each component instance:
|
465
520
|
|
466
521
|
`app/view/products/index.html.erb`
|
522
|
+
|
467
523
|
``` erb
|
468
524
|
<%= render(ProductComponent.with_collection(@products, notice: "hi")) %>
|
469
525
|
```
|
470
526
|
|
471
527
|
`app/components/product_component.rb`
|
528
|
+
|
472
529
|
``` ruby
|
473
530
|
class ProductComponent < ViewComponent::Base
|
474
531
|
with_collection_parameter :item
|
@@ -481,6 +538,7 @@ end
|
|
481
538
|
```
|
482
539
|
|
483
540
|
`app/components/product_component.html.erb`
|
541
|
+
|
484
542
|
``` erb
|
485
543
|
<li>
|
486
544
|
<h2><%= @item.name %></h2>
|
@@ -493,6 +551,7 @@ end
|
|
493
551
|
ViewComponent defines a counter variable matching the parameter name above, followed by `_counter`. To access the variable, add it to `initialize` as an argument:
|
494
552
|
|
495
553
|
`app/components/product_component.rb`
|
554
|
+
|
496
555
|
``` ruby
|
497
556
|
class ProductComponent < ViewComponent::Base
|
498
557
|
def initialize(product:, product_counter:)
|
@@ -503,6 +562,7 @@ end
|
|
503
562
|
```
|
504
563
|
|
505
564
|
`app/components/product_component.html.erb`
|
565
|
+
|
506
566
|
``` erb
|
507
567
|
<li>
|
508
568
|
<%= @counter %> <%= @product.name %>
|
@@ -551,7 +611,7 @@ class UserComponent < ViewComponent::Base
|
|
551
611
|
end
|
552
612
|
```
|
553
613
|
|
554
|
-
###
|
614
|
+
### Writing tests
|
555
615
|
|
556
616
|
Unit test components directly, using the `render_inline` test helper, asserting against the rendered output.
|
557
617
|
|
@@ -561,7 +621,7 @@ Capybara matchers are available if the gem is installed:
|
|
561
621
|
require "view_component/test_case"
|
562
622
|
|
563
623
|
class MyComponentTest < ViewComponent::TestCase
|
564
|
-
|
624
|
+
def test_render_component
|
565
625
|
render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
|
566
626
|
|
567
627
|
assert_selector("span[title='my title']", text: "Hello, World!")
|
@@ -572,7 +632,7 @@ end
|
|
572
632
|
In the absence of `capybara`, assert against the return value of `render_inline`, which is an instance of `Nokogiri::HTML::DocumentFragment`:
|
573
633
|
|
574
634
|
```ruby
|
575
|
-
|
635
|
+
def test_render_component
|
576
636
|
result = render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
|
577
637
|
|
578
638
|
assert_includes result.css("span[title='my title']").to_html, "Hello, World!"
|
@@ -582,7 +642,7 @@ end
|
|
582
642
|
Alternatively, assert against the raw output of the component, which is exposed as `rendered_component`:
|
583
643
|
|
584
644
|
```ruby
|
585
|
-
|
645
|
+
def test_render_component
|
586
646
|
render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
|
587
647
|
|
588
648
|
assert_includes rendered_component, "Hello, World!"
|
@@ -592,7 +652,7 @@ end
|
|
592
652
|
To test components that use `with_content_areas`:
|
593
653
|
|
594
654
|
```ruby
|
595
|
-
|
655
|
+
def test_renders_content_areas_template_with_content
|
596
656
|
render_inline(ContentAreasComponent.new(footer: "Bye!")) do |component|
|
597
657
|
component.with(:title, "Hello!")
|
598
658
|
component.with(:body) { "Have a nice day." }
|
@@ -609,7 +669,7 @@ end
|
|
609
669
|
Use the `with_variant` helper to test specific variants:
|
610
670
|
|
611
671
|
```ruby
|
612
|
-
|
672
|
+
def test_render_component_for_tablet
|
613
673
|
with_variant :tablet do
|
614
674
|
render_inline(TestComponent.new(title: "my title")) { "Hello, tablets!" }
|
615
675
|
|
@@ -619,9 +679,11 @@ end
|
|
619
679
|
```
|
620
680
|
|
621
681
|
### Previewing Components
|
682
|
+
|
622
683
|
`ViewComponent::Preview`, like `ActionMailer::Preview`, provides a way to preview components in isolation:
|
623
684
|
|
624
685
|
`test/components/previews/test_component_preview.rb`
|
686
|
+
|
625
687
|
```ruby
|
626
688
|
class TestComponentPreview < ViewComponent::Preview
|
627
689
|
def with_default_title
|
@@ -649,6 +711,7 @@ and <http://localhost:3000/rails/view_components/test_component/with_content_blo
|
|
649
711
|
It's also possible to set dynamic values from the params by setting them as arguments:
|
650
712
|
|
651
713
|
`test/components/previews/test_component_preview.rb`
|
714
|
+
|
652
715
|
```ruby
|
653
716
|
class TestComponentPreview < ViewComponent::Preview
|
654
717
|
def with_dynamic_title(title: "Test component default")
|
@@ -666,6 +729,7 @@ and [`content_tag`](https://api.rubyonrails.org/classes/ActionView/Helpers/TagHe
|
|
666
729
|
Previews use the application layout by default, but can use a specific layout with the `layout` option:
|
667
730
|
|
668
731
|
`test/components/previews/test_component_preview.rb`
|
732
|
+
|
669
733
|
```ruby
|
670
734
|
class TestComponentPreview < ViewComponent::Preview
|
671
735
|
layout "admin"
|
@@ -677,6 +741,7 @@ end
|
|
677
741
|
You can also set a custom layout to be used by default for previews as well as the preview index pages via the `default_preview_layout` configuration option:
|
678
742
|
|
679
743
|
`config/application.rb`
|
744
|
+
|
680
745
|
```ruby
|
681
746
|
# Set the default layout to app/views/layouts/component_preview.html.erb
|
682
747
|
config.view_component.default_preview_layout = "component_preview"
|
@@ -685,6 +750,7 @@ config.view_component.default_preview_layout = "component_preview"
|
|
685
750
|
Preview classes live in `test/components/previews`, which can be configured using the `preview_paths` option:
|
686
751
|
|
687
752
|
`config/application.rb`
|
753
|
+
|
688
754
|
```ruby
|
689
755
|
config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
|
690
756
|
```
|
@@ -692,6 +758,7 @@ config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
|
|
692
758
|
Previews are served from <http://localhost:3000/rails/view_components> by default. To use a different endpoint, set the `preview_route` option:
|
693
759
|
|
694
760
|
`config/application.rb`
|
761
|
+
|
695
762
|
```ruby
|
696
763
|
config.view_component.preview_route = "/previews"
|
697
764
|
```
|
@@ -703,6 +770,7 @@ This example will make the previews available from <http://localhost:3000/previe
|
|
703
770
|
Given a preview `test/components/previews/cell_component_preview.rb`, template files can be defined at `test/components/previews/cell_component_preview/`:
|
704
771
|
|
705
772
|
`test/components/previews/cell_component_preview.rb`
|
773
|
+
|
706
774
|
```ruby
|
707
775
|
class CellComponentPreview < ViewComponent::Preview
|
708
776
|
def default
|
@@ -711,6 +779,7 @@ end
|
|
711
779
|
```
|
712
780
|
|
713
781
|
`test/components/previews/cell_component_preview/default.html.erb`
|
782
|
+
|
714
783
|
```erb
|
715
784
|
<table class="table">
|
716
785
|
<tbody>
|
@@ -725,6 +794,7 @@ To use a different location for preview templates, pass the `template` argument:
|
|
725
794
|
(the path should be relative to `config.view_component.preview_path`):
|
726
795
|
|
727
796
|
`test/components/previews/cell_component_preview.rb`
|
797
|
+
|
728
798
|
```ruby
|
729
799
|
class CellComponentPreview < ViewComponent::Preview
|
730
800
|
def default
|
@@ -736,6 +806,7 @@ end
|
|
736
806
|
Values from `params` can be accessed through `locals`:
|
737
807
|
|
738
808
|
`test/components/previews/cell_component_preview.rb`
|
809
|
+
|
739
810
|
```ruby
|
740
811
|
class CellComponentPreview < ViewComponent::Preview
|
741
812
|
def default(title: "Default title", subtitle: "A subtitle")
|
@@ -749,11 +820,22 @@ end
|
|
749
820
|
|
750
821
|
Which enables passing in a value with <http://localhost:3000/rails/components/cell_component/default?title=Custom+title&subtitle=Another+subtitle>.
|
751
822
|
|
823
|
+
#### Configuring preview controller
|
824
|
+
|
825
|
+
Previews can be extended to allow users to add authentication, authorization, before actions, or anything that the end user would need to meet their needs using the `preview_controller` option:
|
826
|
+
|
827
|
+
`config/application.rb`
|
828
|
+
|
829
|
+
```ruby
|
830
|
+
config.view_component.preview_controller = "MyPreviewController"
|
831
|
+
```
|
832
|
+
|
752
833
|
#### Configuring TestController
|
753
834
|
|
754
|
-
Component tests
|
835
|
+
Component tests assume the existence of an `ApplicationController` class, which be can be configured using the `test_controller` option:
|
755
836
|
|
756
837
|
`config/application.rb`
|
838
|
+
|
757
839
|
```ruby
|
758
840
|
config.view_component.test_controller = "BaseController"
|
759
841
|
```
|
@@ -763,6 +845,7 @@ config.view_component.test_controller = "BaseController"
|
|
763
845
|
To use RSpec, add the following:
|
764
846
|
|
765
847
|
`spec/rails_helper.rb`
|
848
|
+
|
766
849
|
```ruby
|
767
850
|
require "view_component/test_helpers"
|
768
851
|
|
@@ -776,6 +859,7 @@ Specs created by the generator have access to test helpers like `render_inline`.
|
|
776
859
|
To use component previews:
|
777
860
|
|
778
861
|
`config/application.rb`
|
862
|
+
|
779
863
|
```ruby
|
780
864
|
config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"
|
781
865
|
```
|
@@ -788,7 +872,7 @@ In order to [avoid conflicts](https://github.com/github/view_component/issues/28
|
|
788
872
|
|
789
873
|
With the monkey patch disabled, use `render_component` (or `render_component_to_string`) instead:
|
790
874
|
|
791
|
-
```
|
875
|
+
```erb
|
792
876
|
<%= render_component Component.new(message: "bar") %>
|
793
877
|
```
|
794
878
|
|
@@ -826,6 +910,7 @@ One approach is to use Web Components, which contain all Javascript functionalit
|
|
826
910
|
For example:
|
827
911
|
|
828
912
|
`app/components/comment_component.rb`
|
913
|
+
|
829
914
|
```ruby
|
830
915
|
class CommentComponent < ViewComponent::Base
|
831
916
|
def initialize(comment:)
|
@@ -855,6 +940,7 @@ end
|
|
855
940
|
```
|
856
941
|
|
857
942
|
`app/components/comment_component.html.erb`
|
943
|
+
|
858
944
|
```erb
|
859
945
|
<my-comment comment-id="<%= comment.id %>">
|
860
946
|
<time slot="posted" datetime="<%= comment.created_at.iso8601 %>"><%= comment.created_at.strftime("%b %-d") %></time>
|
@@ -868,6 +954,7 @@ end
|
|
868
954
|
```
|
869
955
|
|
870
956
|
`app/components/comment_component.js`
|
957
|
+
|
871
958
|
```js
|
872
959
|
class Comment extends HTMLElement {
|
873
960
|
styles() {
|
@@ -921,6 +1008,35 @@ application.load(
|
|
921
1008
|
|
922
1009
|
This enables the creation of files such as `app/components/widget_controller.js`, where the controller identifier matches the `data-controller` attribute in the component's HTML template.
|
923
1010
|
|
1011
|
+
After configuring Webpack to load Stimulus controller files from the `components` directory, add the path to `resolved_paths` in `config/webpacker.yml`:
|
1012
|
+
|
1013
|
+
```yml
|
1014
|
+
resolved_paths: ["app/components"]
|
1015
|
+
```
|
1016
|
+
|
1017
|
+
When placing a Stimulus controller inside a sidecar directory, be aware that when referencing the controller [each forward slash in a namespaced controller file’s path becomes two dashes in its identifier](
|
1018
|
+
https://stimulusjs.org/handbook/installing#controller-filenames-map-to-identifiers):
|
1019
|
+
|
1020
|
+
```console
|
1021
|
+
app/components
|
1022
|
+
├── ...
|
1023
|
+
├── example
|
1024
|
+
| ├── component.rb
|
1025
|
+
| ├── component.css
|
1026
|
+
| ├── component.html.erb
|
1027
|
+
| └── component_controller.js
|
1028
|
+
├── ...
|
1029
|
+
```
|
1030
|
+
|
1031
|
+
`component_controller.js`'s Stimulus identifier becomes: `example--component`:
|
1032
|
+
|
1033
|
+
```erb
|
1034
|
+
<div data-controller="example--component">
|
1035
|
+
<input type="text">
|
1036
|
+
<button data-action="click->example--component#greet">Greet</button>
|
1037
|
+
</div>
|
1038
|
+
```
|
1039
|
+
|
924
1040
|
## Frequently Asked Questions
|
925
1041
|
|
926
1042
|
### Can I use other templating languages besides ERB?
|
@@ -952,7 +1068,7 @@ ViewComponent is far from a novel idea! Popular implementations of view componen
|
|
952
1068
|
|
953
1069
|
## Contributing
|
954
1070
|
|
955
|
-
|
1071
|
+
This project is intended to be a safe, welcoming space for collaboration. 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.
|
956
1072
|
|
957
1073
|
## Contributors
|
958
1074
|
|
@@ -1003,6 +1119,11 @@ ViewComponent is built by:
|
|
1003
1119
|
|@johannesengl|@czj|@mrrooijen|@bradparker|@mattbrictson|
|
1004
1120
|
|Berlin, Germany|Paris, France|The Netherlands|Brisbane, Australia|San Francisco|
|
1005
1121
|
|
1122
|
+
|<img src="https://avatars.githubusercontent.com/mixergtz?s=256" alt="mixergtz" width="128" />|<img src="https://avatars.githubusercontent.com/jules2689?s=256" alt="jules2689" width="128" />|<img src="https://avatars.githubusercontent.com/g13ydson?s=256" alt="g13ydson" width="128" />|<img src="https://avatars.githubusercontent.com/swanson?s=256" alt="swanson" width="128" />|
|
1123
|
+
|:---:|:---:|:---:|:---:|
|
1124
|
+
|@mixergtz|@jules2689|@g13ydson|@swanson|
|
1125
|
+
|Medellin, Colombia|Toronto, Canada|João Pessoa, Brazil|Indianapolis, IN|
|
1126
|
+
|
1006
1127
|
## License
|
1007
1128
|
|
1008
1129
|
ViewComponent is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|