Skip to content

Commit 6a2f857

Browse files
authored
Merge pull request #154 from basemate/implementing_issue_153
Implementing issue 153
2 parents 759f9fe + 5d0dec2 commit 6a2f857

File tree

11 files changed

+460
-13
lines changed

11 files changed

+460
-13
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def components(&block)
8686
@nodes = Matestack::Ui::Core::AppNode.build(self, &block)
8787

8888
@nodes.each do |key, node|
89-
@cells[key] = to_cell(key, node["component_name"], node["config"], node["argument"], node["components"], nil)
89+
@cells[key] = to_cell(key, node["component_name"], node["config"], node["argument"], node["components"], nil, node["cached_params"])
9090
end
9191
end
9292

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,14 @@ def initialize(model=nil, options={})
6666
@nodes = {}
6767
@cells = {}
6868
@included_config = options[:included_config]
69+
@cached_params = options[:cached_params]
6970
@rerender = false
7071
@options = options
72+
set_tag_attributes
73+
setup
7174
generate_component_name
7275
generate_children_cells
73-
set_tag_attributes
7476
validate_options
75-
setup
7677
end
7778

7879
def validate_options
@@ -157,7 +158,7 @@ def components(&block)
157158
@nodes = Matestack::Ui::Core::ComponentNode.build(self, nil, &block)
158159

159160
@nodes.each do |key, node|
160-
@cells[key] = to_cell(key, node["component_name"], node["config"], node["argument"], node["components"], node["included_config"])
161+
@cells[key] = to_cell(key, node["component_name"], node["config"], node["argument"], node["components"], node["included_config"], node["cached_params"])
161162
end
162163
end
163164

@@ -197,7 +198,7 @@ def generate_children_cells
197198
#needs refactoring --> in some cases, :component_key, :children, :origin_url, :url_params, :included_config get passed into options[:children] which causes errors
198199
#quickfix: except them from iteration
199200
options[:children].except(:component_key, :children, :origin_url, :url_params, :included_config).each do |key, node|
200-
@children_cells[key] = to_cell("#{@component_key}__#{key}", node["component_name"], node["config"], node["argument"], node["components"], node["included_config"])
201+
@children_cells[key] = to_cell("#{@component_key}__#{key}", node["component_name"], node["config"], node["argument"], node["components"], node["included_config"], node["cached_params"])
201202
end
202203
end
203204
end
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- @children_cells.each do |key, cell|
2+
= cell.call(:show)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module Matestack::Ui::Core::Isolate
2+
class Isolate < Matestack::Ui::Core::Component::Static
3+
4+
def setup
5+
@component_key = @component_key + "__" + @argument.to_s
6+
@component_key = @component_key + "(" + @cached_params.to_json + ")" if @cached_params.present?
7+
@component_config[:component_key] = @component_key
8+
end
9+
10+
end
11+
end

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def components(&block)
5353

5454
def nodes_to_cell
5555
@nodes.each do |key, node|
56-
@cells[key] = to_cell(key, node["component_name"], node["config"], node["argument"], node["components"], node["included_config"])
56+
@cells[key] = to_cell(key, node["component_name"], node["config"], node["argument"], node["components"], node["included_config"], node["cached_params"])
5757
end
5858
end
5959

@@ -65,8 +65,15 @@ def slot(&block)
6565
Matestack::Ui::Core::PageNode.build(self, nil, context[:params], &block)
6666
end
6767

68+
def isolate(&block)
69+
return block
70+
end
71+
72+
6873
def show(component_key=nil, only_page=false)
6974
prepare
75+
return resolve_isolated_component(component_key) if !component_key.nil? && component_key.include?("isolate")
76+
7077
response
7178

7279
render_mode = nil
@@ -91,11 +98,11 @@ def show(component_key=nil, only_page=false)
9198
keys_array = keys_array.drop(keys_array.find_index(page_content_keys[0])+2)
9299
end
93100
node = @nodes.dig(*keys_array)
94-
cell = to_cell(component_key, node["component_name"], node["config"], node["argument"], node["components"], node["included_config"])
101+
cell = to_cell(component_key, node["component_name"], node["config"], node["argument"], node["components"], node["included_config"], node["cached_params"])
95102
return cell.render_content
96103
else
97104
node = @nodes[component_key]
98-
cell = to_cell(component_key, node["component_name"], node["config"], node["argument"], node["components"], node["included_config"])
105+
cell = to_cell(component_key, node["component_name"], node["config"], node["argument"], node["components"], node["included_config"], node["cached_params"])
99106
return cell.render_content
100107
end
101108
rescue
@@ -111,6 +118,27 @@ def page_id
111118

112119
private
113120

121+
def resolve_isolated_component component_key
122+
keys_array = component_key.gsub("__", "__components__").split("__").map {|k| k.to_s}
123+
isolate_keys = keys_array.select{|key| key.match(/^isolate_/)}
124+
keys_array = keys_array.drop(keys_array.find_index(isolate_keys[0])+2)
125+
isolated_scope_method = keys_array[0]
126+
if isolated_scope_method.include?("(")
127+
isolated_scope_method_name = isolated_scope_method.split("(").first
128+
isolated_scope_method_argument = isolated_scope_method.split("(").last.split(")").first
129+
isolated_scope_method_argument = JSON.parse(isolated_scope_method_argument)
130+
isolated_block = self.send(isolated_scope_method_name, isolated_scope_method_argument.with_indifferent_access)
131+
else
132+
isolated_block = self.send(isolated_scope_method)
133+
end
134+
nodes = Matestack::Ui::Core::PageNode.build(
135+
self, nil, context[:params], &isolated_block
136+
)
137+
node = nodes.dig(*keys_array.drop(2))
138+
cell = to_cell(component_key, node["component_name"], node["config"], node["argument"], node["components"], node["included_config"], node["cached_params"])
139+
return cell.render_content
140+
end
141+
114142
def generate_page_name
115143
name_parts = self.class.name.split("::").map { |name| name.underscore }
116144
@page_id = name_parts.join("_")

app/lib/matestack/ui/core/component_node.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,18 @@ def method_missing meth, *args, &block
3232
@hash[current_node]["argument"] = nil
3333
@hash[current_node]["included_config"] = @included_config
3434

35+
if meth == :isolate
36+
raise("isolate > only works on page level currently. component support is comming soon!")
37+
end
38+
3539
if meth == :partial
3640
@hash[current_node]["components"] = @component_instance.send(args.first, *args.drop(1))
3741
elsif meth == :yield_components
3842
@hash[current_node]["component_name"] = "partial"
3943
@hash[current_node]["components"] = @component_instance.send(:get_children)
4044
else
4145
if args.first.is_a?(Hash)
42-
@hash[current_node]["config"] = args.first
46+
@hash[current_node]["config"] = args.first unless meth == :slot
4347
else
4448
@hash[current_node]["argument"] = args.first
4549
end

app/lib/matestack/ui/core/page_node.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,27 @@ def method_missing meth, *args, &block
4343
end
4444
end
4545

46+
if meth == :isolate
47+
if args.second.present?
48+
if args.second.is_a? Hash
49+
if args.second[:cached_params].nil?
50+
raise "isolate > you need to pass params within a hash called 'cached_params'"
51+
else
52+
isolated_block = @page_instance.send(args.first, args.second[:cached_params])
53+
end
54+
else
55+
raise "isolate > you need to pass params within a hash called 'cached_params'"
56+
end
57+
else
58+
isolated_block = @page_instance.send(args.first)
59+
end
60+
@hash[current_node]["components"] = PageNode.build(
61+
@page_instance, nil, @url_params, &isolated_block
62+
)
63+
@hash[current_node]["argument"] = args.first
64+
@hash[current_node]["cached_params"] = args.second[:cached_params] if args.second.present?
65+
end
66+
4667
if meth == :partial
4768
partial_block = @page_instance.send(args.first, *args.drop(1))
4869
@hash[current_node]["components"] = PageNode.build(

app/lib/matestack/ui/core/to_cell.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Matestack::Ui::Core::ToCell
22

3-
def to_cell(key, component_name, config, argument, children, included_config)
3+
def to_cell(key, component_name, config, argument, children, included_config, cached_params=nil)
4+
# p cached_params
45
request_uri = context[:request].env["REQUEST_URI"]
56
query_string = context[:request].env["QUERY_STRING"]
67

@@ -9,6 +10,7 @@ def to_cell(key, component_name, config, argument, children, included_config)
910
config.merge!(origin_url: request_uri.gsub("?" + query_string, ""))
1011
config.merge!(url_params: context[:params])
1112
config.merge!(included_config: included_config)
13+
config.merge!(cached_params: cached_params)
1214

1315
if component_name.start_with?("custom")
1416
return resolve_custom_component(component_name, argument, config)

docs/components/isolate.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# matestack core component: Isolate
2+
3+
Show [specs](/spec/usage/components/isolate_spec.rb)
4+
5+
The `isolate` component allows you to create isolated "scopes", which can be rendered without calling the main response method of a `page`. Used with `async`, only specific isolated scopes are resolved on the serverside rather than resolving the whole UI when asynchronously rerendering parts of the UI. Usage seems to be similar to `partial`, but there are important differences! Read below!
6+
7+
**the isolate/async components currently only work on page-level --> we're working on it in order support the usage of async/isolate within a component [#75](https://github.com/basemate/matestack-ui-core/issues/75)**
8+
9+
## Parameters
10+
11+
The isolate core component accepts the following parameters:
12+
13+
### isolated method (as a symbol) (mandatory)
14+
15+
Just like a partial, using a symbol, a specific part of the UI can be referenced:
16+
17+
```ruby
18+
def response
19+
components{
20+
#a lot of other UI components you want to bypass if you rerender the isolated scope
21+
22+
isolate :my_isolated_scope
23+
24+
#a lot of other UI components you want to bypass if you rerender the isolated scope
25+
26+
#for emphasizing the difference, a similar partial would work like so:
27+
partial :my_partial_scope
28+
}
29+
end
30+
31+
def my_isolated_scope
32+
@some_data = SomeModel.find(42)
33+
34+
isolate{
35+
async rerender_on: "some_event" do
36+
div do
37+
plain @some_data.some_attribute
38+
end
39+
end
40+
}
41+
end
42+
43+
def my_partial_scope
44+
@some_data = SomeModel.find(42)
45+
46+
partial{
47+
async rerender_on: "some_other_event" do
48+
div do
49+
plain @some_data.some_attribute
50+
end
51+
end
52+
}
53+
end
54+
```
55+
56+
As mentioned in the example code above: The `async` requests a new version of `my_isolated_scope` after the event `some_event` is received. But instead of resolving the whole UI and then returning only the desired part, the `isolate` component takes care of bypassing all irrelevant parts of the UI. The `async` rerender action inside the `partial` in contrast would call the whole `response` method of the page, which can lead to significant higher response times.
57+
58+
59+
### Cached Params (Clientside params --> public!)
60+
61+
As the isolated scope is truly encapsulated from the main `response` method while rerendering, simply passing in dynamic params from within the `response` method can not work. As it is often necessary, to render "partials" dynamically, we can use `cached_params` for isolated scopes. These params are stored on the clientside after initial page and then used when performing the rerender action. Using this approach, we can still bypass the main `response` method while using dynamic params.
62+
63+
**Just put simple data into cached_params (simple integers or strings, no hashes etc). Never put sensible data into cached_params as they are visible on the clientside!**
64+
65+
```ruby
66+
def response
67+
components{
68+
#a lot of other UI components you want to bypass if you rerender the isolated scope
69+
70+
[1, 2, 3].each do |id|
71+
isolate :my_isolated_scope, cached_params: { id: id }
72+
end
73+
74+
#a lot of other UI components you want to bypass if you rerender the isolated scope
75+
76+
#for emphasizing the difference, a similar partial would work like so:
77+
[1, 2, 3].each do |id|
78+
partial :my_partial_scope, id
79+
end
80+
}
81+
end
82+
83+
def my_isolated_scope cached_params
84+
@some_data = SomeModel.find(cached_params[:id])
85+
86+
isolate{
87+
async rerender_on: "isolated_rerender_#{cached_params[:id]}" do
88+
div do
89+
plain @some_data.some_attribute
90+
end
91+
end
92+
}
93+
end
94+
95+
def my_partial_scope id
96+
@some_data = SomeModel.find(id)
97+
98+
partial{
99+
async rerender_on: "partial_rerender_#{id}" do
100+
div do
101+
plain @some_data.some_attribute
102+
end
103+
end
104+
}
105+
end
106+
```

spec/usage/base/page_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ def my_action
288288
it "can fill slots of components with access to page instance scope" do
289289

290290
module Matestack::Ui::Core::Example end
291-
291+
292292
module Matestack::Ui::Core::Example::Component
293293
class Component < Matestack::Ui::StaticComponent
294294

@@ -299,8 +299,8 @@ def prepare
299299
def response
300300
components {
301301
div id: "my-component" do
302-
slot options[:my_first_slot]
303-
slot options[:my_second_slot]
302+
slot @options[:my_first_slot]
303+
slot @options[:my_second_slot]
304304
end
305305
}
306306
end

0 commit comments

Comments
 (0)