view_component 2.17.0 → 2.19.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 +127 -75
- data/README.md +148 -29
- data/app/controllers/view_components_controller.rb +26 -3
- data/app/views/view_components/preview.html.erb +5 -1
- data/lib/view_component/base.rb +18 -7
- data/lib/view_component/collection.rb +2 -0
- data/lib/view_component/engine.rb +2 -2
- data/lib/view_component/preview.rb +2 -14
- data/lib/view_component/previewable.rb +8 -0
- data/lib/view_component/slotable.rb +1 -1
- data/lib/view_component/test_helpers.rb +4 -0
- data/lib/view_component/version.rb +1 -1
- metadata +23 -9
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+.
|
@@ -47,6 +50,10 @@ Traditional Rails views have an implicit interface, making it hard to reason abo
|
|
47
50
|
|
48
51
|
ViewComponents use a standard Ruby initializer that clearly defines what is needed to render, making them easier (and safer) to reuse than partials.
|
49
52
|
|
53
|
+
#### Performance
|
54
|
+
|
55
|
+
Based on our [benchmarks](performance/benchmark.rb), ViewComponents are ~10x faster than partials.
|
56
|
+
|
50
57
|
#### Standards
|
51
58
|
|
52
59
|
Views often fail basic Ruby code quality standards: long methods, deep conditional nesting, and mystery guests abound.
|
@@ -63,6 +70,8 @@ Component names end in -`Component`.
|
|
63
70
|
|
64
71
|
Component module names are plural, as for controllers and jobs: `Users::AvatarComponent`
|
65
72
|
|
73
|
+
Name components for what they render, not what they accept. (`AvatarComponent` instead of `UserComponent`)
|
74
|
+
|
66
75
|
#### Quick start
|
67
76
|
|
68
77
|
Use the component generator to create a new ViewComponent.
|
@@ -90,6 +99,7 @@ bin/rails generate component Example title content --template-engine slim
|
|
90
99
|
A ViewComponent is a Ruby file and corresponding template file with the same base name:
|
91
100
|
|
92
101
|
`app/components/test_component.rb`:
|
102
|
+
|
93
103
|
```ruby
|
94
104
|
class TestComponent < ViewComponent::Base
|
95
105
|
def initialize(title:)
|
@@ -99,6 +109,7 @@ end
|
|
99
109
|
```
|
100
110
|
|
101
111
|
`app/components/test_component.html.erb`:
|
112
|
+
|
102
113
|
```erb
|
103
114
|
<span title="<%= @title %>"><%= content %></span>
|
104
115
|
```
|
@@ -124,6 +135,7 @@ Content passed to a ViewComponent as a block is captured and assigned to the `co
|
|
124
135
|
ViewComponents can declare additional content areas. For example:
|
125
136
|
|
126
137
|
`app/components/modal_component.rb`:
|
138
|
+
|
127
139
|
```ruby
|
128
140
|
class ModalComponent < ViewComponent::Base
|
129
141
|
with_content_areas :header, :body
|
@@ -131,6 +143,7 @@ end
|
|
131
143
|
```
|
132
144
|
|
133
145
|
`app/components/modal_component.html.erb`:
|
146
|
+
|
134
147
|
```erb
|
135
148
|
<div class="modal">
|
136
149
|
<div class="header"><%= header %></div>
|
@@ -164,19 +177,17 @@ Returning:
|
|
164
177
|
|
165
178
|
_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
179
|
|
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.
|
180
|
+
Slots enable multiple blocks of content to be passed to a single ViewComponent, reducing the need for sub-components (e.g. ModalHeader, ModalBody).
|
170
181
|
|
171
|
-
|
182
|
+
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
183
|
|
173
|
-
|
184
|
+
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
185
|
|
175
|
-
To learn more about the design of the Slots API, see https://github.com/github/view_component/pull/348.
|
186
|
+
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
187
|
|
177
188
|
##### Defining Slots
|
178
189
|
|
179
|
-
Slots are defined by
|
190
|
+
Slots are defined by `with_slot`:
|
180
191
|
|
181
192
|
`with_slot :header`
|
182
193
|
|
@@ -184,15 +195,16 @@ To define a collection slot, add `collection: true`:
|
|
184
195
|
|
185
196
|
`with_slot :row, collection: true`
|
186
197
|
|
187
|
-
To define a slot with a custom class, pass `class_name`:
|
198
|
+
To define a slot with a custom Ruby class, pass `class_name`:
|
188
199
|
|
189
200
|
`with_slot :body, class_name: 'BodySlot`
|
190
201
|
|
191
|
-
Slot classes
|
202
|
+
_Note: Slot classes must be subclasses of `ViewComponent::Slot`._
|
192
203
|
|
193
204
|
##### Example ViewComponent with Slots
|
194
205
|
|
195
206
|
`# box_component.rb`
|
207
|
+
|
196
208
|
```ruby
|
197
209
|
class BoxComponent < ViewComponent::Base
|
198
210
|
include ViewComponent::Slotable
|
@@ -202,12 +214,12 @@ class BoxComponent < ViewComponent::Base
|
|
202
214
|
with_slot :row, collection: true, class_name: "Row"
|
203
215
|
|
204
216
|
class Header < ViewComponent::Slot
|
205
|
-
def initialize(
|
206
|
-
@
|
217
|
+
def initialize(classes: "")
|
218
|
+
@classes = classes
|
207
219
|
end
|
208
220
|
|
209
|
-
def
|
210
|
-
"Box-header #{@
|
221
|
+
def classes
|
222
|
+
"Box-header #{@classes}"
|
211
223
|
end
|
212
224
|
end
|
213
225
|
|
@@ -237,10 +249,11 @@ end
|
|
237
249
|
```
|
238
250
|
|
239
251
|
`# box_component.html.erb`
|
252
|
+
|
240
253
|
```erb
|
241
254
|
<div class="Box">
|
242
255
|
<% if header %>
|
243
|
-
<div class="<%= header.
|
256
|
+
<div class="<%= header.classes %>">
|
244
257
|
<%= header.content %>
|
245
258
|
</div>
|
246
259
|
<% end %>
|
@@ -260,16 +273,17 @@ end
|
|
260
273
|
<% end %>
|
261
274
|
<% if footer %>
|
262
275
|
<div class="Box-footer">
|
263
|
-
<%= footer %>
|
276
|
+
<%= footer.content %>
|
264
277
|
</div>
|
265
278
|
<% end %>
|
266
279
|
</div>
|
267
280
|
```
|
268
281
|
|
269
282
|
`# index.html.erb`
|
283
|
+
|
270
284
|
```erb
|
271
285
|
<%= render(BoxComponent.new) do |component| %>
|
272
|
-
<% component.slot(:header,
|
286
|
+
<% component.slot(:header, classes: "my-class-name") do %>
|
273
287
|
This is my header!
|
274
288
|
<% end %>
|
275
289
|
<% component.slot(:body) do %>
|
@@ -292,6 +306,7 @@ end
|
|
292
306
|
ViewComponents can render without a template file, by defining a `call` method:
|
293
307
|
|
294
308
|
`app/components/inline_component.rb`:
|
309
|
+
|
295
310
|
```ruby
|
296
311
|
class InlineComponent < ViewComponent::Base
|
297
312
|
def call
|
@@ -318,6 +333,18 @@ class InlineVariantComponent < ViewComponent::Base
|
|
318
333
|
end
|
319
334
|
```
|
320
335
|
|
336
|
+
### Template Inheritance
|
337
|
+
|
338
|
+
Components that subclass another component inherit the parent component's
|
339
|
+
template if they don't define their own template.
|
340
|
+
|
341
|
+
```ruby
|
342
|
+
# If `my_link_component.html.erb` is not defined the component will fall back
|
343
|
+
# to `LinkComponent`s template
|
344
|
+
class MyLinkComponent < LinkComponent
|
345
|
+
end
|
346
|
+
```
|
347
|
+
|
321
348
|
### Sidecar Assets
|
322
349
|
|
323
350
|
ViewComponents supports two options for defining view files.
|
@@ -326,7 +353,7 @@ ViewComponents supports two options for defining view files.
|
|
326
353
|
|
327
354
|
The simplest option is to place the view next to the Ruby component:
|
328
355
|
|
329
|
-
```
|
356
|
+
```console
|
330
357
|
app/components
|
331
358
|
├── ...
|
332
359
|
├── test_component.rb
|
@@ -338,7 +365,7 @@ app/components
|
|
338
365
|
|
339
366
|
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
367
|
|
341
|
-
```
|
368
|
+
```console
|
342
369
|
app/components
|
343
370
|
├── ...
|
344
371
|
├── example_component.rb
|
@@ -347,12 +374,11 @@ app/components
|
|
347
374
|
| ├── example_component.html.erb
|
348
375
|
| └── example_component.js
|
349
376
|
├── ...
|
350
|
-
|
351
377
|
```
|
352
378
|
|
353
379
|
To generate a component with a sidecar directory, use the `--sidecar` flag:
|
354
380
|
|
355
|
-
```
|
381
|
+
```console
|
356
382
|
bin/rails generate component Example title content --sidecar
|
357
383
|
invoke test_unit
|
358
384
|
create test/components/example_component_test.rb
|
@@ -360,6 +386,32 @@ bin/rails generate component Example title content --sidecar
|
|
360
386
|
create app/components/example_component/example_component.html.erb
|
361
387
|
```
|
362
388
|
|
389
|
+
#### Component file inside Sidecar directory
|
390
|
+
|
391
|
+
It's also possible to place the Ruby component file inside the sidecar directory, grouping all related files in the same folder:
|
392
|
+
|
393
|
+
_Note: Avoid giving your containing folder the same name as your `.rb` file or there will be a conflict between Module and Class definitions_
|
394
|
+
|
395
|
+
```console
|
396
|
+
app/components
|
397
|
+
├── ...
|
398
|
+
├── example
|
399
|
+
| ├── component.rb
|
400
|
+
| ├── component.css
|
401
|
+
| ├── component.html.erb
|
402
|
+
| └── component.js
|
403
|
+
├── ...
|
404
|
+
|
405
|
+
```
|
406
|
+
|
407
|
+
The component can then be rendered using the folder name as a namespace:
|
408
|
+
|
409
|
+
```erb
|
410
|
+
<%= render(Example::Component.new(title: "my title")) do %>
|
411
|
+
Hello, World!
|
412
|
+
<% end %>
|
413
|
+
```
|
414
|
+
|
363
415
|
### Conditional Rendering
|
364
416
|
|
365
417
|
Components can implement a `#render?` method to be called after initialization to determine if the component should render.
|
@@ -367,7 +419,8 @@ Components can implement a `#render?` method to be called after initialization t
|
|
367
419
|
Traditionally, the logic for whether to render a view could go in either the component template:
|
368
420
|
|
369
421
|
`app/components/confirm_email_component.html.erb`
|
370
|
-
|
422
|
+
|
423
|
+
```erb
|
371
424
|
<% if user.requires_confirmation? %>
|
372
425
|
<div class="alert">Please confirm your email address.</div>
|
373
426
|
<% end %>
|
@@ -376,6 +429,7 @@ Traditionally, the logic for whether to render a view could go in either the com
|
|
376
429
|
or the view that renders the component:
|
377
430
|
|
378
431
|
`app/views/_banners.html.erb`
|
432
|
+
|
379
433
|
```erb
|
380
434
|
<% if current_user.requires_confirmation? %>
|
381
435
|
<%= render(ConfirmEmailComponent.new(user: current_user)) %>
|
@@ -385,6 +439,7 @@ or the view that renders the component:
|
|
385
439
|
Using the `#render?` hook simplifies the view:
|
386
440
|
|
387
441
|
`app/components/confirm_email_component.rb`
|
442
|
+
|
388
443
|
```ruby
|
389
444
|
class ConfirmEmailComponent < ViewComponent::Base
|
390
445
|
def initialize(user:)
|
@@ -398,13 +453,15 @@ end
|
|
398
453
|
```
|
399
454
|
|
400
455
|
`app/components/confirm_email_component.html.erb`
|
401
|
-
|
456
|
+
|
457
|
+
```erb
|
402
458
|
<div class="banner">
|
403
459
|
Please confirm your email address.
|
404
460
|
</div>
|
405
461
|
```
|
406
462
|
|
407
463
|
`app/views/_banners.html.erb`
|
464
|
+
|
408
465
|
```erb
|
409
466
|
<%= render(ConfirmEmailComponent.new(user: current_user)) %>
|
410
467
|
```
|
@@ -416,6 +473,7 @@ _To assert that a component has not been rendered, use `refute_component_rendere
|
|
416
473
|
Components can define a `before_render` method to be called before a component is rendered, when `helpers` is able to be used:
|
417
474
|
|
418
475
|
`app/components/confirm_email_component.rb`
|
476
|
+
|
419
477
|
```ruby
|
420
478
|
class MyComponent < ViewComponent::Base
|
421
479
|
def before_render
|
@@ -429,11 +487,13 @@ end
|
|
429
487
|
Use `with_collection` to render a ViewComponent with a collection:
|
430
488
|
|
431
489
|
`app/view/products/index.html.erb`
|
490
|
+
|
432
491
|
``` erb
|
433
492
|
<%= render(ProductComponent.with_collection(@products)) %>
|
434
493
|
```
|
435
494
|
|
436
495
|
`app/components/product_component.rb`
|
496
|
+
|
437
497
|
``` ruby
|
438
498
|
class ProductComponent < ViewComponent::Base
|
439
499
|
def initialize(product:)
|
@@ -449,6 +509,7 @@ end
|
|
449
509
|
Use `with_collection_parameter` to change the name of the collection parameter:
|
450
510
|
|
451
511
|
`app/components/product_component.rb`
|
512
|
+
|
452
513
|
``` ruby
|
453
514
|
class ProductComponent < ViewComponent::Base
|
454
515
|
with_collection_parameter :item
|
@@ -464,11 +525,13 @@ end
|
|
464
525
|
Additional arguments besides the collection are passed to each component instance:
|
465
526
|
|
466
527
|
`app/view/products/index.html.erb`
|
528
|
+
|
467
529
|
``` erb
|
468
530
|
<%= render(ProductComponent.with_collection(@products, notice: "hi")) %>
|
469
531
|
```
|
470
532
|
|
471
533
|
`app/components/product_component.rb`
|
534
|
+
|
472
535
|
``` ruby
|
473
536
|
class ProductComponent < ViewComponent::Base
|
474
537
|
with_collection_parameter :item
|
@@ -481,6 +544,7 @@ end
|
|
481
544
|
```
|
482
545
|
|
483
546
|
`app/components/product_component.html.erb`
|
547
|
+
|
484
548
|
``` erb
|
485
549
|
<li>
|
486
550
|
<h2><%= @item.name %></h2>
|
@@ -493,6 +557,7 @@ end
|
|
493
557
|
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
558
|
|
495
559
|
`app/components/product_component.rb`
|
560
|
+
|
496
561
|
``` ruby
|
497
562
|
class ProductComponent < ViewComponent::Base
|
498
563
|
def initialize(product:, product_counter:)
|
@@ -503,6 +568,7 @@ end
|
|
503
568
|
```
|
504
569
|
|
505
570
|
`app/components/product_component.html.erb`
|
571
|
+
|
506
572
|
``` erb
|
507
573
|
<li>
|
508
574
|
<%= @counter %> <%= @product.name %>
|
@@ -551,7 +617,7 @@ class UserComponent < ViewComponent::Base
|
|
551
617
|
end
|
552
618
|
```
|
553
619
|
|
554
|
-
###
|
620
|
+
### Writing tests
|
555
621
|
|
556
622
|
Unit test components directly, using the `render_inline` test helper, asserting against the rendered output.
|
557
623
|
|
@@ -619,9 +685,11 @@ end
|
|
619
685
|
```
|
620
686
|
|
621
687
|
### Previewing Components
|
688
|
+
|
622
689
|
`ViewComponent::Preview`, like `ActionMailer::Preview`, provides a way to preview components in isolation:
|
623
690
|
|
624
691
|
`test/components/previews/test_component_preview.rb`
|
692
|
+
|
625
693
|
```ruby
|
626
694
|
class TestComponentPreview < ViewComponent::Preview
|
627
695
|
def with_default_title
|
@@ -649,6 +717,7 @@ and <http://localhost:3000/rails/view_components/test_component/with_content_blo
|
|
649
717
|
It's also possible to set dynamic values from the params by setting them as arguments:
|
650
718
|
|
651
719
|
`test/components/previews/test_component_preview.rb`
|
720
|
+
|
652
721
|
```ruby
|
653
722
|
class TestComponentPreview < ViewComponent::Preview
|
654
723
|
def with_dynamic_title(title: "Test component default")
|
@@ -666,6 +735,7 @@ and [`content_tag`](https://api.rubyonrails.org/classes/ActionView/Helpers/TagHe
|
|
666
735
|
Previews use the application layout by default, but can use a specific layout with the `layout` option:
|
667
736
|
|
668
737
|
`test/components/previews/test_component_preview.rb`
|
738
|
+
|
669
739
|
```ruby
|
670
740
|
class TestComponentPreview < ViewComponent::Preview
|
671
741
|
layout "admin"
|
@@ -674,9 +744,19 @@ class TestComponentPreview < ViewComponent::Preview
|
|
674
744
|
end
|
675
745
|
```
|
676
746
|
|
747
|
+
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:
|
748
|
+
|
749
|
+
`config/application.rb`
|
750
|
+
|
751
|
+
```ruby
|
752
|
+
# Set the default layout to app/views/layouts/component_preview.html.erb
|
753
|
+
config.view_component.default_preview_layout = "component_preview"
|
754
|
+
```
|
755
|
+
|
677
756
|
Preview classes live in `test/components/previews`, which can be configured using the `preview_paths` option:
|
678
757
|
|
679
758
|
`config/application.rb`
|
759
|
+
|
680
760
|
```ruby
|
681
761
|
config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
|
682
762
|
```
|
@@ -684,6 +764,7 @@ config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
|
|
684
764
|
Previews are served from <http://localhost:3000/rails/view_components> by default. To use a different endpoint, set the `preview_route` option:
|
685
765
|
|
686
766
|
`config/application.rb`
|
767
|
+
|
687
768
|
```ruby
|
688
769
|
config.view_component.preview_route = "/previews"
|
689
770
|
```
|
@@ -695,6 +776,7 @@ This example will make the previews available from <http://localhost:3000/previe
|
|
695
776
|
Given a preview `test/components/previews/cell_component_preview.rb`, template files can be defined at `test/components/previews/cell_component_preview/`:
|
696
777
|
|
697
778
|
`test/components/previews/cell_component_preview.rb`
|
779
|
+
|
698
780
|
```ruby
|
699
781
|
class CellComponentPreview < ViewComponent::Preview
|
700
782
|
def default
|
@@ -703,6 +785,7 @@ end
|
|
703
785
|
```
|
704
786
|
|
705
787
|
`test/components/previews/cell_component_preview/default.html.erb`
|
788
|
+
|
706
789
|
```erb
|
707
790
|
<table class="table">
|
708
791
|
<tbody>
|
@@ -717,6 +800,7 @@ To use a different location for preview templates, pass the `template` argument:
|
|
717
800
|
(the path should be relative to `config.view_component.preview_path`):
|
718
801
|
|
719
802
|
`test/components/previews/cell_component_preview.rb`
|
803
|
+
|
720
804
|
```ruby
|
721
805
|
class CellComponentPreview < ViewComponent::Preview
|
722
806
|
def default
|
@@ -728,6 +812,7 @@ end
|
|
728
812
|
Values from `params` can be accessed through `locals`:
|
729
813
|
|
730
814
|
`test/components/previews/cell_component_preview.rb`
|
815
|
+
|
731
816
|
```ruby
|
732
817
|
class CellComponentPreview < ViewComponent::Preview
|
733
818
|
def default(title: "Default title", subtitle: "A subtitle")
|
@@ -746,6 +831,7 @@ Which enables passing in a value with <http://localhost:3000/rails/components/ce
|
|
746
831
|
Component tests and previews assume the existence of an `ApplicationController` class, which be can be configured using the `test_controller` option:
|
747
832
|
|
748
833
|
`config/application.rb`
|
834
|
+
|
749
835
|
```ruby
|
750
836
|
config.view_component.test_controller = "BaseController"
|
751
837
|
```
|
@@ -755,6 +841,7 @@ config.view_component.test_controller = "BaseController"
|
|
755
841
|
To use RSpec, add the following:
|
756
842
|
|
757
843
|
`spec/rails_helper.rb`
|
844
|
+
|
758
845
|
```ruby
|
759
846
|
require "view_component/test_helpers"
|
760
847
|
|
@@ -768,6 +855,7 @@ Specs created by the generator have access to test helpers like `render_inline`.
|
|
768
855
|
To use component previews:
|
769
856
|
|
770
857
|
`config/application.rb`
|
858
|
+
|
771
859
|
```ruby
|
772
860
|
config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"
|
773
861
|
```
|
@@ -780,7 +868,7 @@ In order to [avoid conflicts](https://github.com/github/view_component/issues/28
|
|
780
868
|
|
781
869
|
With the monkey patch disabled, use `render_component` (or `render_component_to_string`) instead:
|
782
870
|
|
783
|
-
```
|
871
|
+
```erb
|
784
872
|
<%= render_component Component.new(message: "bar") %>
|
785
873
|
```
|
786
874
|
|
@@ -818,6 +906,7 @@ One approach is to use Web Components, which contain all Javascript functionalit
|
|
818
906
|
For example:
|
819
907
|
|
820
908
|
`app/components/comment_component.rb`
|
909
|
+
|
821
910
|
```ruby
|
822
911
|
class CommentComponent < ViewComponent::Base
|
823
912
|
def initialize(comment:)
|
@@ -847,6 +936,7 @@ end
|
|
847
936
|
```
|
848
937
|
|
849
938
|
`app/components/comment_component.html.erb`
|
939
|
+
|
850
940
|
```erb
|
851
941
|
<my-comment comment-id="<%= comment.id %>">
|
852
942
|
<time slot="posted" datetime="<%= comment.created_at.iso8601 %>"><%= comment.created_at.strftime("%b %-d") %></time>
|
@@ -860,6 +950,7 @@ end
|
|
860
950
|
```
|
861
951
|
|
862
952
|
`app/components/comment_component.js`
|
953
|
+
|
863
954
|
```js
|
864
955
|
class Comment extends HTMLElement {
|
865
956
|
styles() {
|
@@ -913,6 +1004,29 @@ application.load(
|
|
913
1004
|
|
914
1005
|
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.
|
915
1006
|
|
1007
|
+
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](
|
1008
|
+
https://stimulusjs.org/handbook/installing#controller-filenames-map-to-identifiers):
|
1009
|
+
|
1010
|
+
```console
|
1011
|
+
app/components
|
1012
|
+
├── ...
|
1013
|
+
├── example
|
1014
|
+
| ├── component.rb
|
1015
|
+
| ├── component.css
|
1016
|
+
| ├── component.html.erb
|
1017
|
+
| └── component_controller.js
|
1018
|
+
├── ...
|
1019
|
+
```
|
1020
|
+
|
1021
|
+
`component_controller.js`'s Stimulus identifier becomes: `example--component`:
|
1022
|
+
|
1023
|
+
```erb
|
1024
|
+
<div data-controller="example--component">
|
1025
|
+
<input type="text">
|
1026
|
+
<button data-action="click->example--component#greet">Greet</button>
|
1027
|
+
</div>
|
1028
|
+
```
|
1029
|
+
|
916
1030
|
## Frequently Asked Questions
|
917
1031
|
|
918
1032
|
### Can I use other templating languages besides ERB?
|
@@ -944,7 +1058,7 @@ ViewComponent is far from a novel idea! Popular implementations of view componen
|
|
944
1058
|
|
945
1059
|
## Contributing
|
946
1060
|
|
947
|
-
|
1061
|
+
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.
|
948
1062
|
|
949
1063
|
## Contributors
|
950
1064
|
|
@@ -990,10 +1104,15 @@ ViewComponent is built by:
|
|
990
1104
|
|@maxbeizer|@franco|@tbroad-ramsey|@jensljungblad|@bbugh|
|
991
1105
|
|Nashville, TN|Switzerland|Spring Hill, TN|New York, NY|Austin, TX|
|
992
1106
|
|
993
|
-
|<img src="https://avatars.githubusercontent.com/johannesengl?s=256" alt="johannesengl" width="128" />|<img src="https://avatars.githubusercontent.com/czj?s=256" alt="czj" width="128" />|<img src="https://avatars.githubusercontent.com/mrrooijen?s=256" alt="mrrooijen" width="128" />|
|
994
|
-
|
995
|
-
|@johannesengl|@czj|@mrrooijen|
|
996
|
-
|Berlin, Germany|Paris, France|The Netherlands|
|
1107
|
+
|<img src="https://avatars.githubusercontent.com/johannesengl?s=256" alt="johannesengl" width="128" />|<img src="https://avatars.githubusercontent.com/czj?s=256" alt="czj" width="128" />|<img src="https://avatars.githubusercontent.com/mrrooijen?s=256" alt="mrrooijen" width="128" />|<img src="https://avatars.githubusercontent.com/bradparker?s=256" alt="bradparker" width="128" />|<img src="https://avatars.githubusercontent.com/mattbrictson?s=256" alt="mattbrictson" width="128" />|
|
1108
|
+
|:---:|:---:|:---:|:---:|:---:|
|
1109
|
+
|@johannesengl|@czj|@mrrooijen|@bradparker|@mattbrictson|
|
1110
|
+
|Berlin, Germany|Paris, France|The Netherlands|Brisbane, Australia|San Francisco|
|
1111
|
+
|
1112
|
+
|<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" />|
|
1113
|
+
|:---:|:---:|
|
1114
|
+
|@mixergtz|@jules2689|
|
1115
|
+
|Medellin, Colombia|Toronto, Canada|
|
997
1116
|
|
998
1117
|
## License
|
999
1118
|
|