Skip to content

Commit 8fdb82f

Browse files
waiting-for-develia
authored andcommitted
Document admin's view components extensibility
This document describes the working implementation of the admin's view components extensibility.
1 parent 685d4f4 commit 8fdb82f

File tree

2 files changed

+192
-2
lines changed

2 files changed

+192
-2
lines changed

admin/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
A Rails engine that provides an administrative interface to the Solidus ecommerce platform.
44

5-
- [Customizing tailwind](docs/customizing_tailwind.md)
6-
75
## Development
86

7+
- [Customizing tailwind](docs/customizing_tailwind.md)
8+
- [Customizing view components](docs/customizing_view_components.md)
9+
910
### Adding components to Solidus Admin
1011

1112
When using the component generator from within the admin folder it will generate the component in the library
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Customizing view components
2+
3+
Solidus Admin uses [view components](https://viewcomponent.org/) to render the views. Components are
4+
a pattern for breaking up the view layer into small, reusable pieces, easy to
5+
reason about and test.
6+
7+
All the components Solidus Admin uses are located in the [`app/components`](../app/components) folder of the
8+
`solidus_admin` gem. As you can see, they are organized in a particular folder structure:
9+
10+
- All of them are under the `SolidusAdmin` namespace.
11+
- They are grouped in sidecar directories, where the main component file and
12+
all its related files (assets, i18n files, etc.) live together.
13+
14+
For instance, the component for the main navigation is located in
15+
[`app/components/solidus_admin/main_nav/component.rb`](../app/components/solidus_admin/main_nav/component.rb).
16+
17+
Solidus Admin components are designed to be easily customizable by the host
18+
application. Because of that, if you look at how they are designed, you'll find
19+
a series of patterns are followed consistently:
20+
21+
- Components are always resolved from a global registry in
22+
`SolidusAdmin::Container` instead of being referenced by their constant. For
23+
example, we call `component('main_nav')` instead of referencing
24+
`SolidusAdmin::MainNav::Component` directly. As you'll see later, this makes
25+
it easy to replace or tweak a component.
26+
- When a component needs to render another component, it gets it as an argument
27+
for its `#initialize` method and assigns it to an instance variable ending in
28+
`_component`. When rendering from the template, it calls the `render` method
29+
on the instance variable instead of resolving the component in-line. Because
30+
of that, switching a nested component only requires modifying its
31+
initializer.
32+
- Those components given on initialization are always defaulted to its expected
33+
value resolved from the container. That means that callers don't need to
34+
provide them explicitly, and that they can be replaced by custom components.
35+
- In any case, Solidus Admin components initializers only take keyword
36+
arguments.
37+
38+
A picture is worth a thousand words, so let's depict how this works with an
39+
example:
40+
41+
```ruby
42+
# app/components/solidus_admin/foo/component.rb
43+
class SolidusAdmin::Foo::Component < SolidusAdmin::BaseComponent
44+
def initialize(bar_component: component('bar'))
45+
@bar_component = bar_component
46+
end
47+
48+
erb_template <<~ERB
49+
<div>
50+
<%= render @bar_component.new %>
51+
</div>
52+
ERB
53+
end
54+
# render component('foo').new
55+
```
56+
57+
## Customizing components
58+
59+
Some of the customizations detailed below require you to match Solidus Admin's
60+
component paths in your application. For instance, if we talk about the
61+
component in `solidus_admin` gem's
62+
`app/components/solidus_admin/main_nav/component.rb`, the matching path in your
63+
application would be
64+
`app/components/my_application/solidus_admin/main_nav/component.rb`, where
65+
`my_application` is the underscored name of your application (you can get it by
66+
running `Rails.application.class.module_parent_name`).
67+
68+
### Replacing a component's template
69+
70+
> ⓘ This is not possible yet because of
71+
> https://github.com/ViewComponent/view_component/issues/411, but it would
72+
> probably be worth trying to find a way to make it work. Otherwise, we can
73+
> describe how to override the `erb_template` call.
74+
75+
In the most typical case, you'll only need to replace the template used by a
76+
component. You can do that by creating a new template with a maching path in
77+
your application. For example, to replace the main nav template, you can create:
78+
79+
```erb
80+
<%# app/components/my_application/solidus_admin/main_nav/template.html.erb %>
81+
<nav class="my_own_classes">
82+
<%=
83+
render main_nav_item_component.with_collection(
84+
sorted_items
85+
)
86+
%>
87+
</nav>
88+
```
89+
90+
### Prepending or appending to a component's template
91+
92+
In some situations, you might only need to add some markup before or after a
93+
component. You can easily do that by rendering the Solidus Admin component and
94+
adding your markup before or after it.
95+
96+
```erb
97+
<%# app/components/my_application/solidus_admin/main_nav/template.html.erb %>
98+
<h1>MY STORE ADMINISTRATION</h1>
99+
<%= render SolidusAdmin::MainNav::Component.new %>
100+
```
101+
102+
### Replacing a component
103+
104+
You can replace a component by creating a new one with a matching path in your
105+
application.
106+
107+
There are two considerations to keep in mind:
108+
109+
- Be aware that other components might be using the component you're replacing.
110+
They should only be using its `#initialize` method, so make sure to keep
111+
compatibility with it when they're called.
112+
- Solidus Admin's components always inherit from
113+
[SolidusAdmin::BaseComponent](../app/components/solidus_admin/base_component.rb).
114+
You can consider doing the same if you need to use one of its helpers.
115+
116+
For example, the following replaces the main nav component:
117+
118+
```ruby
119+
# app/components/my_application/solidus_admin/main_nav/component.rb
120+
class MyApplication::SolidusAdmin::MainNav::Component < SolidusAdmin::BaseComponent
121+
# do your thing
122+
end
123+
```
124+
125+
If you need more control, you can explicitly register your component in the
126+
Solidus Admin container instead of using an implicit path:
127+
128+
> ⓘ Right now, that will raise an error when the application is reloaded. We
129+
> need to fix it.
130+
131+
```ruby
132+
# config/initalizers/solidus_admin.rb
133+
Rails.application.config.to_prepare do
134+
SolidusAdmin::Container['components.main_nav.component'] = OtherNamespace::NavComponent
135+
end
136+
```
137+
138+
### Tweaking a component
139+
140+
If you only need to tweak a component, you can always inherit from it in a
141+
matching path from within your application (or manually register in the
142+
container) and override the methods you need to change:
143+
144+
```ruby
145+
# app/components/my_application/solidus_admin/main_nav/component.rb
146+
class MyApplication::SolidusAdmin::MainNav::Component < ::SolidusAdmin::MainNav::Component
147+
def sorted_items
148+
super.reverse
149+
end
150+
end
151+
```
152+
153+
Be aware that this approach comes with an important trade-off: You'll need to
154+
keep your component in sync with the original one as it changes on future updates.
155+
For instance, in the example above, the component is overriding a private
156+
method, so there's no guarantee that it will continue to exist in the future
157+
without being deprecated, as we only guarantee public API stability.
158+
159+
### Replacing or tweaking a nested component
160+
161+
If you need to customize a nested component that is used in a single place, you
162+
can do that by following our previous instructions on [replacing a
163+
component](#replacing-a-component) or [tweaking a component](#tweaking-a-component).
164+
165+
However, if the component you want to customize is used in multiple places, you
166+
can replace the default component called from the parent one.
167+
168+
For the sake of example, let's say that the main navigation item component is used in
169+
multiple places, but you only need to change the one rendered from the main
170+
navigation. To begin with, you'd need to manually register in the container the
171+
component you want to use:
172+
173+
```ruby
174+
Rails.application.config.to_prepare do
175+
SolidusAdmin::Container.register('components.main_nav_item_foo.component', MyApplication::SolidusAdmin::MainNavItemFoo::Component)
176+
end
177+
```
178+
179+
Then, you can change the default value of the `main_nav_item_component`
180+
argument from the top-level nav component:
181+
182+
```ruby
183+
# app/components/my_application/solidus_admin/main_nav/component.rb
184+
class MyApplication::SolidusAdmin::MainNav::Component < ::SolidusAdmin::MainNav::Component
185+
def initialize(main_nav_item_component: component('main_nav_item_foo'), **kwargs)
186+
super
187+
end
188+
end
189+
```

0 commit comments

Comments
 (0)