view_component_reflex 1.7.1 → 2.1.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.
- checksums.yaml +4 -4
- data/README.md +27 -0
- data/app/components/view_component_reflex/component.rb +12 -122
- data/app/reflexes/view_component_reflex/reflex.rb +140 -0
- data/lib/view_component_reflex.rb +1 -1
- data/lib/view_component_reflex/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 415195dd61211d392433d4e85963a6807871143f255b3264e2f7b4871bb42c42
|
4
|
+
data.tar.gz: 69f6030f02557d4d9886e3cfc00c738c66d748eb7a1a4b8149136ddaef8f8e9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e12ff695c21dc3f6c140c9d7aa7ccc854eeaeacf2a8071dc519f87c4ca1447464c01f8dfc9c200c0ac720dd864a71788fcc80f74072577c4c833743ccea29c79
|
7
|
+
data.tar.gz: ed7a85c244de733490571e7c4b529ce7b8dd295c0f6c1a5d57b15f40460b4a3737f2669bf8f28845d80a06b2ed721de49bc810c7ac9477629ead664635b2f2ee
|
data/README.md
CHANGED
@@ -108,6 +108,33 @@ def collection_key
|
|
108
108
|
end
|
109
109
|
```
|
110
110
|
|
111
|
+
### stimulate(target, data)
|
112
|
+
Stimulate another reflex from within your component. This typically requires the key of the component you're stimulating
|
113
|
+
which can be passed in via parameters.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
def initialize(parent_key)
|
117
|
+
@parent_key = parent_key
|
118
|
+
end
|
119
|
+
|
120
|
+
def stimulate_other
|
121
|
+
stimulate("OtherComponent#method", { key: @parent_key })
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
### refresh!(selectors)
|
126
|
+
Refresh a specific element on the page. Using this will implicitly run `prevent_render!`.
|
127
|
+
If you want to render a specific element, as well as the component, a common pattern would be to pass `selector` as one of the parameters
|
128
|
+
|
129
|
+
```
|
130
|
+
def my_method
|
131
|
+
refresh! '#my-special-element', selector
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
### selector
|
136
|
+
Returns the unique selector for this component. Useful to pass to `refresh!` when refreshing custom elements.
|
137
|
+
|
111
138
|
### prevent_refresh!
|
112
139
|
By default, VCR will re-render your component after it executes your method. `revent_refresh!` prevents this from happening.
|
113
140
|
|
@@ -2,122 +2,8 @@ module ViewComponentReflex
|
|
2
2
|
class Component < ViewComponent::Base
|
3
3
|
class << self
|
4
4
|
def init_stimulus_reflex
|
5
|
-
|
6
|
-
@stimulus_reflex
|
7
|
-
def refresh!(*selectors)
|
8
|
-
save_state
|
9
|
-
|
10
|
-
# If the component has instance variables omitted from state,
|
11
|
-
# we can't render it to string from here because those instance
|
12
|
-
# variables will be missing. In that case, set the selectors to the
|
13
|
-
# default selector and manually morph the page
|
14
|
-
if selectors.empty? && !component.can_render_to_string?
|
15
|
-
selectors.push selector
|
16
|
-
end
|
17
|
-
|
18
|
-
# If we're just updating the component itself, we can
|
19
|
-
# directly render it instead of rendering the entire page again
|
20
|
-
if selectors.empty?
|
21
|
-
refresh_component!
|
22
|
-
else
|
23
|
-
@channel.send :render_page_and_broadcast_morph, self, selectors, {
|
24
|
-
"dataset" => element.dataset.to_h,
|
25
|
-
"args" => [],
|
26
|
-
"attrs" => element.attributes.to_h,
|
27
|
-
"selectors" => ["body"],
|
28
|
-
"target" => "#{self.class.name}##{method_name}",
|
29
|
-
"url" => request.url,
|
30
|
-
"permanent_attribute_name" => permanent_attribute_name
|
31
|
-
}
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def refresh_component!
|
36
|
-
# The component can't figure out the key when we render from here
|
37
|
-
# Luckily we already know the key, so we can manually override it
|
38
|
-
component.tap do |k|
|
39
|
-
k.define_singleton_method(:key) do
|
40
|
-
element.dataset[:key]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
html = controller.render_component_to_string(component)
|
44
|
-
document = Nokogiri::HTML(html)
|
45
|
-
morph selector, document.css("#{selector} > *").to_s
|
46
|
-
end
|
47
|
-
|
48
|
-
def selector
|
49
|
-
"[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]"
|
50
|
-
end
|
51
|
-
|
52
|
-
def refresh_all!
|
53
|
-
morph :body, render_page(self)
|
54
|
-
end
|
55
|
-
|
56
|
-
# SR's delegate_call_to_reflex in channel.rb
|
57
|
-
# uses method to gather the method parameters, but since we're abusing
|
58
|
-
# method_missing here, that'll always fail
|
59
|
-
def method(name)
|
60
|
-
name.to_sym.to_proc
|
61
|
-
end
|
62
|
-
|
63
|
-
def respond_to_missing?(name, _ = false)
|
64
|
-
!!name.to_proc
|
65
|
-
end
|
66
|
-
|
67
|
-
def method_missing(name, *args)
|
68
|
-
super unless respond_to_missing?(name)
|
69
|
-
state.each do |k, v|
|
70
|
-
component.instance_variable_set(k, v)
|
71
|
-
end
|
72
|
-
name.to_proc.call(component, *args)
|
73
|
-
refresh! unless @prevent_refresh
|
74
|
-
end
|
75
|
-
|
76
|
-
def prevent_refresh!
|
77
|
-
@prevent_refresh = true
|
78
|
-
end
|
79
|
-
|
80
|
-
define_method :component_class do
|
81
|
-
@component_class ||= klass
|
82
|
-
end
|
83
|
-
|
84
|
-
private :component_class
|
85
|
-
|
86
|
-
private
|
87
|
-
|
88
|
-
def stimulus_controller
|
89
|
-
component_class.stimulus_controller
|
90
|
-
end
|
91
|
-
|
92
|
-
def component
|
93
|
-
return @component if @component
|
94
|
-
@component = component_class.allocate
|
95
|
-
reflex = self
|
96
|
-
exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!]
|
97
|
-
exposed_methods.each do |meth|
|
98
|
-
@component.define_singleton_method(meth) do |*a|
|
99
|
-
reflex.send(meth, *a)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
@component
|
103
|
-
end
|
104
|
-
|
105
|
-
def set_state(new_state = {})
|
106
|
-
ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
|
107
|
-
end
|
108
|
-
|
109
|
-
def state
|
110
|
-
ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
|
111
|
-
end
|
112
|
-
|
113
|
-
def save_state
|
114
|
-
new_state = {}
|
115
|
-
component.safe_instance_variables.each do |k|
|
116
|
-
new_state[k] = component.instance_variable_get(k)
|
117
|
-
end
|
118
|
-
set_state(new_state)
|
119
|
-
end
|
120
|
-
})
|
5
|
+
@stimulus_reflex ||= Object.const_set(name + "Reflex", Class.new(Reflex))
|
6
|
+
@stimulus_reflex.component_class = self
|
121
7
|
end
|
122
8
|
end
|
123
9
|
|
@@ -129,10 +15,6 @@ module ViewComponentReflex
|
|
129
15
|
helpers.controller.instance_variable_get(:@stimulus_reflex)
|
130
16
|
end
|
131
17
|
|
132
|
-
def can_render_to_string?
|
133
|
-
omitted_from_state.empty?
|
134
|
-
end
|
135
|
-
|
136
18
|
def component_controller(opts_or_tag = :div, opts = {}, &blk)
|
137
19
|
self.class.init_stimulus_reflex
|
138
20
|
init_key
|
@@ -152,6 +34,10 @@ module ViewComponentReflex
|
|
152
34
|
content_tag tag, capture(&blk), options
|
153
35
|
end
|
154
36
|
|
37
|
+
def can_render_to_string?
|
38
|
+
omitted_from_state.empty?
|
39
|
+
end
|
40
|
+
|
155
41
|
# key is required if you're using state
|
156
42
|
# We can't initialize the session state in the initial method
|
157
43
|
# because it doesn't have a view_context yet
|
@@ -205,7 +91,11 @@ module ViewComponentReflex
|
|
205
91
|
else
|
206
92
|
initial_state = ViewComponentReflex::Engine.state_adapter.state(request, "#{@key}_initial")
|
207
93
|
ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
|
208
|
-
|
94
|
+
instance_value = instance_variable_get(k)
|
95
|
+
if permit_parameter?(initial_state[k], instance_value)
|
96
|
+
ViewComponentReflex::Engine.state_adapter.set_state(request, controller, "#{@key}_initial", { k => instance_value })
|
97
|
+
ViewComponentReflex::Engine.state_adapter.set_state(request, controller, @key, { k => instance_value })
|
98
|
+
else
|
209
99
|
instance_variable_set(k, v)
|
210
100
|
end
|
211
101
|
end
|
@@ -223,7 +113,7 @@ module ViewComponentReflex
|
|
223
113
|
[
|
224
114
|
:@view_context, :@lookup_context, :@view_renderer, :@view_flow,
|
225
115
|
:@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
|
226
|
-
:@helpers, :@controller, :@request, :@
|
116
|
+
:@helpers, :@controller, :@request, :@tag_builder
|
227
117
|
]
|
228
118
|
end
|
229
119
|
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module ViewComponentReflex
|
2
|
+
class Reflex < StimulusReflex::Reflex
|
3
|
+
include CableReady::Broadcaster
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :component_class
|
7
|
+
end
|
8
|
+
|
9
|
+
def refresh!(primary_selector = nil, *rest)
|
10
|
+
save_state
|
11
|
+
|
12
|
+
if primary_selector.nil? && !component.can_render_to_string?
|
13
|
+
primary_selector = selector
|
14
|
+
end
|
15
|
+
if primary_selector
|
16
|
+
prevent_refresh!
|
17
|
+
|
18
|
+
controller.process(url_params[:action])
|
19
|
+
document = Nokogiri::HTML(controller.response.body)
|
20
|
+
[primary_selector, *rest].each do |s|
|
21
|
+
html = document.css(s)
|
22
|
+
if html.present?
|
23
|
+
cable_ready[channel.stream_name].morph(
|
24
|
+
selector: s,
|
25
|
+
html: html.inner_html,
|
26
|
+
children_only: true,
|
27
|
+
permanent_attribute_name: "data-reflex-permanent"
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
else
|
32
|
+
refresh_component!
|
33
|
+
end
|
34
|
+
cable_ready.broadcast
|
35
|
+
end
|
36
|
+
|
37
|
+
def refresh_component!
|
38
|
+
component.tap do |k|
|
39
|
+
k.define_singleton_method(:key) do
|
40
|
+
element.dataset[:key]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
document = Nokogiri::HTML(controller.render_component_to_string(component))
|
44
|
+
cable_ready[channel.stream_name].morph(
|
45
|
+
selector: selector,
|
46
|
+
children_only: true,
|
47
|
+
html: document.css(selector).inner_html,
|
48
|
+
permanent_attribute_name: "data-reflex-permanent"
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def refresh_all!
|
53
|
+
refresh!("body")
|
54
|
+
end
|
55
|
+
|
56
|
+
def selector
|
57
|
+
"[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]"
|
58
|
+
end
|
59
|
+
|
60
|
+
# SR's delegate_call_to_reflex in channel.rb
|
61
|
+
# uses method to gather the method parameters, but since we're abusing
|
62
|
+
# method_missing here, that'll always fail
|
63
|
+
def method(name)
|
64
|
+
name.to_sym.to_proc
|
65
|
+
end
|
66
|
+
|
67
|
+
def respond_to_missing?(name, _ = false)
|
68
|
+
!!name.to_proc
|
69
|
+
end
|
70
|
+
|
71
|
+
before_reflex do |a|
|
72
|
+
a.send a.method_name
|
73
|
+
throw :abort
|
74
|
+
end
|
75
|
+
|
76
|
+
def method_missing(name, *args)
|
77
|
+
super unless respond_to_missing?(name)
|
78
|
+
state.each do |k, v|
|
79
|
+
component.instance_variable_set(k, v)
|
80
|
+
end
|
81
|
+
name.to_proc.call(component, *args)
|
82
|
+
refresh! unless @prevent_refresh
|
83
|
+
end
|
84
|
+
|
85
|
+
def prevent_refresh!
|
86
|
+
@prevent_refresh = true
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def component_class
|
92
|
+
self.class.component_class
|
93
|
+
end
|
94
|
+
|
95
|
+
def stimulus_controller
|
96
|
+
component_class.stimulus_controller
|
97
|
+
end
|
98
|
+
|
99
|
+
def stimulate(target, data)
|
100
|
+
dataset = {}
|
101
|
+
data.each do |k, v|
|
102
|
+
dataset["data-#{k}"] = v.to_s
|
103
|
+
end
|
104
|
+
channel.receive({
|
105
|
+
"target" => target,
|
106
|
+
"attrs" => element.attributes.to_h.symbolize_keys,
|
107
|
+
"dataset" => dataset
|
108
|
+
})
|
109
|
+
end
|
110
|
+
|
111
|
+
def component
|
112
|
+
return @component if @component
|
113
|
+
@component = component_class.allocate
|
114
|
+
reflex = self
|
115
|
+
exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!, :selector, :stimulate]
|
116
|
+
exposed_methods.each do |meth|
|
117
|
+
@component.define_singleton_method(meth) do |*a|
|
118
|
+
reflex.send(meth, *a)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
@component
|
122
|
+
end
|
123
|
+
|
124
|
+
def set_state(new_state = {})
|
125
|
+
ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
|
126
|
+
end
|
127
|
+
|
128
|
+
def state
|
129
|
+
ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
|
130
|
+
end
|
131
|
+
|
132
|
+
def save_state
|
133
|
+
new_state = {}
|
134
|
+
component.safe_instance_variables.each do |k|
|
135
|
+
new_state[k] = component.instance_variable_get(k)
|
136
|
+
end
|
137
|
+
set_state(new_state)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: view_component_reflex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua LeBlanc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-07-
|
11
|
+
date: 2020-07-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -36,28 +36,28 @@ dependencies:
|
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
39
|
+
version: '0'
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
46
|
+
version: '0'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: view_component
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
53
|
+
version: '0'
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - ">="
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
60
|
+
version: '0'
|
61
61
|
description: Allow stimulus reflexes in a view component
|
62
62
|
email:
|
63
63
|
- joshleblanc94@gmail.com
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- README.md
|
70
70
|
- Rakefile
|
71
71
|
- app/components/view_component_reflex/component.rb
|
72
|
+
- app/reflexes/view_component_reflex/reflex.rb
|
72
73
|
- lib/view_component_reflex.rb
|
73
74
|
- lib/view_component_reflex/engine.rb
|
74
75
|
- lib/view_component_reflex/state_adapter/memory.rb
|