Skip to content

Commit 8a4f47c

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

File tree

9 files changed

+136
-132
lines changed

9 files changed

+136
-132
lines changed

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

Lines changed: 6 additions & 10 deletions
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
@@ -242,18 +242,14 @@ def add_child(child_class, *args, &block)
242242
unless args.empty? || args[0].keys.all? { |key| [:defer, :public_options, :rerender_on, :init_on, :rerender_delay].include? key }
243243
raise "isolated components can only take params in a public_options hash, which will be exposed to the client side in order to perform an async request with these params."
244244
end
245-
# if args.any? { |arg| arg[:init_on].present? } && @matestack_skip_defer == true
246-
# skip_deferred_child_response = true
247-
# end
245+
if args.any? { |arg| arg[:init_on].present? } && @matestack_skip_defer == true
246+
skip_deferred_child_response = true
247+
end
248248
end
249249

250250
if self.class < Matestack::Ui::Core::Isolate::Isolate
251-
parent_context_included_config = @current_parent_context.get_included_config
252-
if parent_context_included_config.nil?
253-
parent_context_included_config = { isolated_parent_class: self.class.name }
254-
else
255-
parent_context_included_config.merge!({ isolated_parent_class: self.class.name })
256-
end
251+
parent_context_included_config = @current_parent_context.get_included_config || {}
252+
parent_context_included_config.merge!({ isolated_parent_class: self.class.name })
257253
args_with_context = add_context_to_options(args,parent_context_included_config)
258254
else
259255
args_with_context = add_context_to_options(args, @current_parent_context.get_included_config)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
module Matestack::Ui::Core::Isolate
22
class Isolate < Matestack::Ui::Core::Component::Dynamic
3-
43
vue_js_component_name "matestack-ui-core-isolate"
54

65
def initialize(*args)
@@ -22,6 +21,7 @@ def setup
2221
@component_config[:defer] = @options[:defer]
2322
@component_config[:rerender_on] = @options[:rerender_on]
2423
@component_config[:rerender_delay] = @options[:rerender_delay]
24+
@component_config[:init_on] = @options[:init_on]
2525
end
2626

2727
def loading_classes

app/lib/matestack/ui/core/rendering/main_renderer.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,7 @@ def render(controller_instance, page_class, options)
2828
# isolated component rendering
2929
component_class = params[:component_class]
3030
page_instance = page_class.new(controller_instance: controller_instance, context: context)
31-
if params[:public_options].present?
32-
public_options = JSON.parse(params[:public_options]).with_indifferent_access
33-
else
34-
public_options = nil
35-
end
31+
public_options = JSON.parse(params[:public_options]).with_indifferent_access rescue nil
3632
render_isolated_component(component_class, page_instance, controller_instance, context, public_options)
3733
elsif (params[:component_class].present? && params[:component_key].present?)
3834
# async component rerendering from isolated context

docs/api/base/backend/isolate.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Matestack Core Component: Isolate
2+
3+
Feel free to check out the [component specs](/spec/usage/components/dynamic/isolate).
4+
5+
The isolate component allows you to create components, which can be rendered independently. It means that isolate components are rendered without calling the response method of your page, which gives you the possibility to rerender components dynamically without rerendering the whole UI.
6+
7+
In addition it is possible to combine isolate components and async components. If an async component inside an isolate component gets rerendered the server only needs to resolve the isolate scope instead of the whole UI.
8+
9+
If not configured, the isolate component gets rendered on initial pageload. You can prevent this by passing a `defer` or `init_on` option. See below for further details.
10+
11+
## Parameters
12+
13+
The isolate core component accepts the following parameters:
14+
15+
### defer
16+
17+
The option defer lets you delay the initial component rendering. If you set defer to a positive integer or `true` the isolate component will not be rendered on initial page load. Instead it will be rendered with an asynchronous request only resolving the isolate component.
18+
19+
If `defer` is set to `true` the asynchronous requests gets triggered as soon as the initial page is loaded.
20+
21+
If `defer` is set to a positive integer (including zero) the asynchronous request is delayed by the given amount in ms.
22+
23+
### rerender_on
24+
25+
The `rerender_on` options lets you define events on which the component will be rerenderd asynchronously. Events on which the component should be rerendered are specified via a comma seperated string, for example `rerender_on: 'event_one, event_two`.
26+
27+
### rerender_delay
28+
29+
The `rerender_delay` option lets you specify a delay in ms after which the asynchronous request is emitted to rerender the component. It can for example be used to smooth out loading animations, preventing flickering in the UI for fast responses.
30+
31+
### init_on
32+
33+
With `init_on` you can specify events on which the isolate components gets initialized. Specify events on which the component should be initially rendered via a comma seperated string. When receiving a matching event the isolate component is rendered asynchronously. If you also specified the `defer` option the asynchronous rerendering call will be delayed by the given time in ms of the defer option. If `defer` is set to `true` the rendering will not be delayed.
34+
35+
### public_options
36+
37+
You can pass data as a hash to your custom isolate component with the `public_options` option. This data is inside the isolate component accessible via a hash with indifferent access, for example `public_options[:item_id]`. All data contained in the `public_options` will be passed as json to the corresponding vue component, which means this data is visible on the client side as it is rendered in the vue component config. So be careful what data you pass into `public_options`!
38+
39+
Due to the isolation of the component the data needs to be stored on the client side as to encapsulate the component from the rest of the UI.
40+
For example: You want to render a collection of models in single components which should be able to rerender asynchronously without rerendering the whole UI. Since we do not rerender the whole UI there is no way the component can know which of the models it should rerender. Therefore passing for example the id in the public_options hash gives you the possibility to access the id in an async request and fetch the model again for rerendering. See below for examples.
41+
42+
## Loading State and animations
43+
44+
TODO
45+
46+
## Examples
47+
48+
### Example 1 - Simple Isolate
49+
50+
Create a custom component inheriting from the isolate component
51+
52+
```ruby
53+
class MyIsolated < Matestack::Ui::Core::IsolatedComponent
54+
def response
55+
div id: 'my-isolated-wrapper' do
56+
plain I18n.l(DateTime.now)
57+
end
58+
end
59+
end
60+
```
61+
Register your custom component
62+
```ruby
63+
module ComponentsRegistry
64+
Matestack::Ui::Core::Component::Registry.register_components(
65+
my_isolated: MyIsolated
66+
)
67+
```
68+
And use it on your page
69+
```ruby
70+
class Home < Matestack::Ui::Page
71+
def response
72+
heading size: 1, text: 'Welcome'
73+
my_isolated
74+
end
75+
end
76+
```
77+
78+
This will render a h1 with the content welcome and the localized current datetime inside the isolated component. The isolated component gets rendered with the initial page load, because the defer options is not set.
79+
80+
### Example 2 - Simple Deferred Isolated
81+
```ruby
82+
class Home < Matestack::Ui::Page
83+
def response
84+
heading size: 1, text: 'Welcome'
85+
my_isolated defer: true,
86+
my_isolated defer: 2000
87+
end
88+
end
89+
```
90+
91+
By specifying the `defer` option both calls to the custom isolated components will not get rendered on initial page load. Instead the component with `defer: true` will get rendered as soon as the initial page load is done and the component with `defer: 2000` will be rendered 2000ms after the initial page load is done. Which means that the second my_isolated component will show the datetime with 2s more on the clock then the first one.
92+
93+
### Example 3 - Rerender On Isolate Component
94+
95+
```ruby
96+
class Home < Matestack::Ui::Page
97+
def response
98+
heading size: 1, text: 'Welcome'
99+
my_isolated rerender_on: 'update_time'
100+
onclick emit: 'update_time' do
101+
button 'Update Time!'
102+
end
103+
end
104+
end
105+
```
106+
107+
`rerender_on: 'update_time'` tells the custom isolated component to rerender its content asynchronously whenever the event `update_time` is emitted. In this case every time the button is pressed the event is emitted and the isolated component gets rerendered, showing the new timestamp afterwards. In contrast to async components only the `MyIsolated` component is rendered on the server side instead of the whole UI.
108+
109+
### Example 4 - Rerender Isolated Component with a delay
110+
111+
TODO
112+
113+
### Example 5 - Initialize isolated component on a event
114+
115+
TODO
116+
117+
### Example 6 - Use custom data in isolated components
118+
119+
TODO

docs/api/components/isolate.md

Lines changed: 0 additions & 106 deletions
This file was deleted.

spec/dummy/public/packs-test/manifest.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"application.js": "/packs-test/js/application-2243df8a41c9bc9efb48.js",
3-
"application.js.map": "/packs-test/js/application-2243df8a41c9bc9efb48.js.map",
2+
"application.js": "/packs-test/js/application-44d457b3da0e0ea1a542.js",
3+
"application.js.map": "/packs-test/js/application-44d457b3da0e0ea1a542.js.map",
44
"entrypoints": {
55
"application": {
66
"js": [
7-
"/packs-test/js/application-2243df8a41c9bc9efb48.js"
7+
"/packs-test/js/application-44d457b3da0e0ea1a542.js"
88
],
99
"js.map": [
10-
"/packs-test/js/application-2243df8a41c9bc9efb48.js.map"
10+
"/packs-test/js/application-44d457b3da0e0ea1a542.js.map"
1111
]
1212
}
1313
}
0 Bytes
Binary file not shown.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def response
6464
end
6565
end
6666

67+
TouchedElementsCounter.instance.reset
6768
visit "/example"
6869
# the first request resolves the whole page --> counter + 2
6970
# the isolated component requests its content right after mount --> counter + 2
@@ -97,6 +98,7 @@ def response
9798
end
9899
end
99100

101+
TouchedElementsCounter.instance.reset
100102
visit "/example"
101103
# the first request resolves the whole page --> counter + 2
102104
# the isolated component requests its content right after mount --> counter + 2
@@ -133,6 +135,7 @@ def response
133135
end
134136
end
135137

138+
TouchedElementsCounter.instance.reset
136139
visit "/example"
137140
# the first request resolves the whole page --> counter + 2
138141
# the isolated component should be rendered with the page --> counter + 2

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,14 @@ def response
139139

140140
end
141141

142+
TouchedElementsCounter.instance.reset
142143
visit "/example"
143144
# the first request resolves the whole page --> counter + 2
144145
# the isolated component requests its content right after mount --> counter + 2
145146
expect(TouchedElementsCounter.instance.counter).to eq 4
146147
expect(page).to have_css('.some-isolated-component')
147148

148149
TouchedElementsCounter.instance.reset
149-
150150
visit "/example?component_class=SomeIsolatedComponent"
151151
expect(page).to have_css('.some-isolated-component')
152152

@@ -697,23 +697,19 @@ def response
697697
it "can wait for initial rendering until event" do
698698

699699
class ExamplePage < Matestack::Ui::Page
700-
701700
def response
702701
div id: "page-div" do
703702
some_isolated_component init_on: "some-event, or-another"
704703
end
705704
end
706-
707705
end
708706

709707
visit "/example"
710-
708+
sleep 1
711709
expect(page).not_to have_css '#isolated-component-timestamp', visible: :all
712710

713711
page.execute_script('MatestackUiCore.matestackEventHub.$emit("some-event")')
714-
715712
expect(page).to have_css '#isolated-component-timestamp', visible: :all
716-
717713
end
718714

719715
it "can inherit from a isolate application base class?" do

0 commit comments

Comments
 (0)