Skip to content

Commit 72f4ebe

Browse files
author
Nils Henning
committed
[FEATURE][TASK] isolate component deferrable, document isolate component
1 parent 8a4f47c commit 72f4ebe

File tree

5 files changed

+85
-11
lines changed

5 files changed

+85
-11
lines changed

app/concepts/matestack/ui/core/component/base.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def add_child(child_class, *args, &block)
231231
# the childs content in order to respond to the subsequent component rendering call with
232232
# the childs content. In this case: "I should be deferred"
233233
skip_deferred_child_response = false
234-
if child_class == Matestack::Ui::Core::Async::Async || child_class == Matestack::Ui::Core::Isolate::Isolate
234+
if child_class <= Matestack::Ui::Core::Async::Async || child_class < Matestack::Ui::Core::Isolate::Isolate
235235
if args.any? { |arg| arg[:defer].present? } && @matestack_skip_defer == true
236236
skip_deferred_child_response = true
237237
end

app/concepts/matestack/ui/core/isolate/isolate.haml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
%component{dynamic_tag_attributes}
22
%div{loading_classes.merge(class: "matestack-isolated-component-container")}
3-
%div{loading_classes.merge(class: "loading-state-element-wrapper")}
4-
=loading_state_element
3+
- if loading_state_element.present?
4+
%div{loading_classes.merge(class: "loading-state-element-wrapper")}
5+
=loading_state_element
56
- unless options[:defer] || options[:init_on]
67
%div{class: "matestack-isolated-component-wrapper", "v-if": "isolatedTemplate == null", "v-bind:class": "{ 'loading': loading === true }"}
78
= render_isolated_content

docs/api/base/backend/isolate.md

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,86 @@ end
108108

109109
### Example 4 - Rerender Isolated Component with a delay
110110

111-
TODO
111+
```ruby
112+
class Home < Matestack::Ui::Page
113+
def response
114+
heading size: 1, text: 'Welcome'
115+
my_isolated rerender_on: 'update_time', rerender_delay: 300
116+
onclick emit: 'update_time' do
117+
button 'Update Time!'
118+
end
119+
end
120+
end
121+
```
122+
123+
The my_isolated component will be rerendered 300ms after the `update_time` event is emitted
112124

113125
### Example 5 - Initialize isolated component on a event
114126

115-
TODO
127+
```ruby
128+
class Home < Matestack::Ui::Page
129+
def response
130+
heading size: 1, text: 'Welcome'
131+
my_isolated init_on: 'init_time'
132+
onclick emit: 'init_time' do
133+
button 'Init Time!'
134+
end
135+
end
136+
end
137+
```
138+
139+
With `init_on: 'init_time'` you can specify an event on which the isolated component should be initialized. When you click the button the event `init_time` is emitted and the isolated component asynchronously requests its content.
116140

117141
### Example 6 - Use custom data in isolated components
118142

119-
TODO
143+
Like described above it is possible to use custom data in your isolated components. Just pass them as a hash to `public_options` and use them in your isolated component. Be careful, because `public_options` are visible in the raw html response from the server as they get passed to a vue component.
144+
145+
Lets render a collection of models and each of them should rerender when a user clicks a corresponding refresh button. Our model is called `Match`, representing a soccer match. It has an attribute called score with the current match score.
146+
147+
At first we create a custom isolated component.
148+
```ruby
149+
class Components::Match::IsolatedScore < Matestack::Ui::IsolatedComponent
150+
151+
def prepare
152+
@match = Match.find_by(public_options[:id])
153+
end
154+
155+
def response
156+
div class: 'score' do
157+
plain @match.score
158+
end
159+
onclick emit: "update_match_#{@match.id}" do
160+
button 'Refresh'
161+
end
162+
end
163+
164+
end
165+
```
166+
After that we register our new custom component.
167+
```ruby
168+
module ComponentsRegistry
169+
Matestack::Ui::Core::Component::Registry.register_components(
170+
match_isolated_score: Components::Match::IsolatedScore
171+
)
172+
```
173+
Make sure your registry is loaded in your controller. In our case we include our registry in the `ApplicationController`.
174+
```ruby
175+
class ApplicationController < ActionController::Base
176+
include Matestack::Ui::Core::ApplicationHelper
177+
include Components::Registry
178+
end
179+
```
180+
Now we create our page which will render a list of matches.
181+
```ruby
182+
class Match::Pages::Index < Matestack::Ui::Page
183+
def response
184+
Match.all.each do |match|
185+
match_isolated_score public_options: { id: match.id }, rerender_on: "update_match_#{match.id}"
186+
end
187+
end
188+
end
189+
```
190+
191+
This page will render a match_isolated_score component for each match.
192+
If one of the isolated components gets rerendered we need the id in order to fetch the correct match. Because the server only resolves the isolated component instead of the whole UI it does not know which match exactly is requested unless the client requests a rerender with the match id. This is why `public_options` options are passed to the client side vue component.
193+
So if match two should be rerendered the client requests the match_isolated_score component with `public_options: { id: 2 }`. With this information our isolated component can fetch the match and rerender itself.

spec/test/components/dynamic/isolate/defer_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def response
6666

6767
TouchedElementsCounter.instance.reset
6868
visit "/example"
69+
sleep 1
6970
# the first request resolves the whole page --> counter + 2
7071
# the isolated component requests its content right after mount --> counter + 2
7172
expect(TouchedElementsCounter.instance.counter).to eq 4
@@ -100,13 +101,12 @@ def response
100101

101102
TouchedElementsCounter.instance.reset
102103
visit "/example"
103-
# the first request resolves the whole page --> counter + 2
104-
# the isolated component requests its content right after mount --> counter + 2
105-
expect(TouchedElementsCounter.instance.counter).to eq 4
104+
expect(TouchedElementsCounter.instance.counter).to eq 2
106105
# isolated component should not be rendered directly
107106
expect(page).not_to have_css('.some-isolated-component', wait: 1)
108107
# isolated component should be rendered after 2000ms
109108
expect(page).to have_css('.some-isolated-component', wait: 3)
109+
expect(TouchedElementsCounter.instance.counter).to eq 4
110110

111111
TouchedElementsCounter.instance.reset
112112

spec/test/components/dynamic/isolate/isolate_spec.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ def response
1414
end
1515

1616
register_self_as(:some_other_component)
17-
1817
end
1918

2019
class SomeIsolatedComponent < Matestack::Ui::IsolatedComponent
@@ -143,8 +142,8 @@ def response
143142
visit "/example"
144143
# the first request resolves the whole page --> counter + 2
145144
# the isolated component requests its content right after mount --> counter + 2
146-
expect(TouchedElementsCounter.instance.counter).to eq 4
147145
expect(page).to have_css('.some-isolated-component')
146+
expect(TouchedElementsCounter.instance.counter).to eq 4
148147

149148
TouchedElementsCounter.instance.reset
150149
visit "/example?component_class=SomeIsolatedComponent"

0 commit comments

Comments
 (0)