view_component_reflex 1.6.0 → 2.0.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 +20 -2
- data/app/components/view_component_reflex/component.rb +33 -102
- data/app/reflexes/view_component_reflex/reflex.rb +123 -0
- data/lib/view_component_reflex.rb +8 -7
- data/lib/view_component_reflex/engine.rb +13 -13
- data/lib/view_component_reflex/state_adapter/memory.rb +25 -0
- data/lib/view_component_reflex/version.rb +3 -3
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a3e7ed47e432ef756d8359ace66447aedd995d240eb423007ec868c9dfa0402
|
4
|
+
data.tar.gz: bb836fc180d285454b516ced91b92cc309320d9257c5da71a17c2789631fcd0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 594bf37cfa1ef0798df664220190d694d3c164ee25516b0220ba6f884b4bf3667a52cfb0668d7d0ae2d684d17c18296862f5ae2473ace3b23ac8496f29825ba6
|
7
|
+
data.tar.gz: d82970ac5abd4aea2ffb675f261ba7c6b840e1f866f9b93b51c368cead4fff06d9ac1e7c8211f679f0f58a4d6b78557fe2c553b66bec982ddaf7d1cf0c5e8433
|
data/README.md
CHANGED
@@ -70,8 +70,8 @@ end
|
|
70
70
|
```
|
71
71
|
|
72
72
|
### omitted_from_state
|
73
|
-
Return an array of instance variables you want to omit from state.
|
74
|
-
|
73
|
+
Return an array of instance variables you want to omit from state. Only really useful if you're using the session state
|
74
|
+
adapter, and you have an instance variable that can't be serialized.
|
75
75
|
|
76
76
|
```ruby
|
77
77
|
def omitted_from_state
|
@@ -108,6 +108,19 @@ def collection_key
|
|
108
108
|
end
|
109
109
|
```
|
110
110
|
|
111
|
+
### refresh!(selectors)
|
112
|
+
Refresh a specific element on the page. Using this will implicitly run `prevent_render!`.
|
113
|
+
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
|
114
|
+
|
115
|
+
```
|
116
|
+
def my_method
|
117
|
+
refresh! '#my-special-element', selector
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
### selector
|
122
|
+
Returns the unique selector for this component. Useful to pass to `refresh!` when refreshing custom elements.
|
123
|
+
|
111
124
|
### prevent_refresh!
|
112
125
|
By default, VCR will re-render your component after it executes your method. `revent_refresh!` prevents this from happening.
|
113
126
|
|
@@ -181,6 +194,11 @@ end
|
|
181
194
|
<% end
|
182
195
|
```
|
183
196
|
|
197
|
+
## State
|
198
|
+
|
199
|
+
By default, view_component_reflex stores component state in memory. You can optionally set the state adapter
|
200
|
+
to use the session by changing `config.state_adapter` to `ViewComponentReflex::StateAdapter::Session`
|
201
|
+
|
184
202
|
## Custom State Adapters
|
185
203
|
|
186
204
|
ViewComponentReflex uses session for its state by default. To change this, add
|
@@ -2,95 +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!(primary_selector = "[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]", *selectors)
|
8
|
-
save_state
|
9
|
-
@channel.send :render_page_and_broadcast_morph, self, [primary_selector, *selectors], {
|
10
|
-
"dataset" => element.dataset.to_h,
|
11
|
-
"args" => [],
|
12
|
-
"attrs" => element.attributes.to_h,
|
13
|
-
"selectors" => ["body"],
|
14
|
-
"target" => "#{self.class.name}##{method_name}",
|
15
|
-
"url" => request.url,
|
16
|
-
"permanent_attribute_name" => "data-reflex-permanent"
|
17
|
-
}
|
18
|
-
end
|
19
|
-
|
20
|
-
def refresh_all!
|
21
|
-
refresh!("body")
|
22
|
-
end
|
23
|
-
|
24
|
-
# SR's delegate_call_to_reflex in channel.rb
|
25
|
-
# uses method to gather the method parameters, but since we're abusing
|
26
|
-
# method_missing here, that'll always fail
|
27
|
-
def method(name)
|
28
|
-
name.to_sym.to_proc
|
29
|
-
end
|
30
|
-
|
31
|
-
def respond_to_missing?(name, _ = false)
|
32
|
-
!!name.to_proc
|
33
|
-
end
|
34
|
-
|
35
|
-
before_reflex do |a|
|
36
|
-
a.send a.method_name
|
37
|
-
throw :abort
|
38
|
-
end
|
39
|
-
|
40
|
-
def method_missing(name, *args)
|
41
|
-
super unless respond_to_missing?(name)
|
42
|
-
state.each do |k, v|
|
43
|
-
component.instance_variable_set(k, v)
|
44
|
-
end
|
45
|
-
name.to_proc.call(component, *args)
|
46
|
-
refresh! unless @prevent_refresh
|
47
|
-
end
|
48
|
-
|
49
|
-
def prevent_refresh!
|
50
|
-
@prevent_refresh = true
|
51
|
-
end
|
52
|
-
|
53
|
-
define_method :component_class do
|
54
|
-
@component_class ||= klass
|
55
|
-
end
|
56
|
-
|
57
|
-
private :component_class
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def stimulus_controller
|
62
|
-
component_class.stimulus_controller
|
63
|
-
end
|
64
|
-
|
65
|
-
def component
|
66
|
-
return @component if @component
|
67
|
-
@component = component_class.allocate
|
68
|
-
reflex = self
|
69
|
-
exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!]
|
70
|
-
exposed_methods.each do |meth|
|
71
|
-
@component.define_singleton_method(meth) do |*a|
|
72
|
-
reflex.send(meth, *a)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
@component
|
76
|
-
end
|
77
|
-
|
78
|
-
def set_state(new_state = {})
|
79
|
-
ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
|
80
|
-
end
|
81
|
-
|
82
|
-
def state
|
83
|
-
ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
|
84
|
-
end
|
85
|
-
|
86
|
-
def save_state
|
87
|
-
new_state = {}
|
88
|
-
component.instance_variables.each do |k|
|
89
|
-
new_state[k] = component.instance_variable_get(k)
|
90
|
-
end
|
91
|
-
set_state(new_state)
|
92
|
-
end
|
93
|
-
})
|
5
|
+
@stimulus_reflex ||= Object.const_set(name + "Reflex", Class.new(Reflex))
|
6
|
+
@stimulus_reflex.component_class = self
|
94
7
|
end
|
95
8
|
end
|
96
9
|
|
@@ -121,6 +34,10 @@ module ViewComponentReflex
|
|
121
34
|
content_tag tag, capture(&blk), options
|
122
35
|
end
|
123
36
|
|
37
|
+
def can_render_to_string?
|
38
|
+
omitted_from_state.empty?
|
39
|
+
end
|
40
|
+
|
124
41
|
# key is required if you're using state
|
125
42
|
# We can't initialize the session state in the initial method
|
126
43
|
# because it doesn't have a view_context yet
|
@@ -133,7 +50,7 @@ module ViewComponentReflex
|
|
133
50
|
@key = key
|
134
51
|
end
|
135
52
|
|
136
|
-
def reflex_tag(reflex, name, content_or_options_with_block =
|
53
|
+
def reflex_tag(reflex, name, content_or_options_with_block = {}, options = {}, escape = true, &block)
|
137
54
|
action, method = reflex.to_s.split("->")
|
138
55
|
if method.nil?
|
139
56
|
method = action
|
@@ -165,18 +82,10 @@ module ViewComponentReflex
|
|
165
82
|
|
166
83
|
def key
|
167
84
|
# initialize session state
|
168
|
-
if !stimulus_reflex? ||
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
blacklist = [
|
173
|
-
:@view_context, :@lookup_context, :@view_renderer, :@view_flow,
|
174
|
-
:@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
|
175
|
-
:@helpers, :@controller, :@request, :@content
|
176
|
-
]
|
177
|
-
instance_variables.reject { |k| blacklist.include?(k) }.each do |k|
|
178
|
-
new_state[k] = instance_variable_get(k) unless omitted_from_state.include?(k)
|
179
|
-
end
|
85
|
+
if !stimulus_reflex? || ViewComponentReflex::Engine.state_adapter.state(request, @key).empty?
|
86
|
+
|
87
|
+
new_state = create_safe_state
|
88
|
+
|
180
89
|
ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
|
181
90
|
ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", new_state)
|
182
91
|
else
|
@@ -190,8 +99,30 @@ module ViewComponentReflex
|
|
190
99
|
@key
|
191
100
|
end
|
192
101
|
|
102
|
+
def safe_instance_variables
|
103
|
+
instance_variables - unsafe_instance_variables
|
104
|
+
end
|
105
|
+
|
193
106
|
private
|
194
107
|
|
108
|
+
def unsafe_instance_variables
|
109
|
+
[
|
110
|
+
:@view_context, :@lookup_context, :@view_renderer, :@view_flow,
|
111
|
+
:@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
|
112
|
+
:@helpers, :@controller, :@request, :@content, :@tag_builder
|
113
|
+
]
|
114
|
+
end
|
115
|
+
|
116
|
+
def create_safe_state
|
117
|
+
new_state = {}
|
118
|
+
|
119
|
+
# this will almost certainly break
|
120
|
+
safe_instance_variables.each do |k|
|
121
|
+
new_state[k] = instance_variable_get(k) unless omitted_from_state.include?(k)
|
122
|
+
end
|
123
|
+
new_state
|
124
|
+
end
|
125
|
+
|
195
126
|
def merge_data_attributes(options, attributes)
|
196
127
|
data = options[:data]
|
197
128
|
if data.nil?
|
@@ -0,0 +1,123 @@
|
|
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].inner_html(
|
24
|
+
selector: s,
|
25
|
+
html: html.inner_html,
|
26
|
+
children_only: true
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
refresh_component!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def refresh_component!
|
36
|
+
component.tap do |k|
|
37
|
+
k.define_singleton_method(:key) do
|
38
|
+
element.dataset[:key]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
cable_ready[channel.stream_name].outer_html(
|
42
|
+
selector: selector,
|
43
|
+
html: controller.render_component_to_string(component)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def refresh_all!
|
48
|
+
refresh!("body")
|
49
|
+
end
|
50
|
+
|
51
|
+
def selector
|
52
|
+
"[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]"
|
53
|
+
end
|
54
|
+
|
55
|
+
# SR's delegate_call_to_reflex in channel.rb
|
56
|
+
# uses method to gather the method parameters, but since we're abusing
|
57
|
+
# method_missing here, that'll always fail
|
58
|
+
def method(name)
|
59
|
+
name.to_sym.to_proc
|
60
|
+
end
|
61
|
+
|
62
|
+
def respond_to_missing?(name, _ = false)
|
63
|
+
!!name.to_proc
|
64
|
+
end
|
65
|
+
|
66
|
+
before_reflex do |a|
|
67
|
+
a.send a.method_name
|
68
|
+
throw :abort
|
69
|
+
end
|
70
|
+
|
71
|
+
def method_missing(name, *args)
|
72
|
+
super unless respond_to_missing?(name)
|
73
|
+
state.each do |k, v|
|
74
|
+
component.instance_variable_set(k, v)
|
75
|
+
end
|
76
|
+
name.to_proc.call(component, *args)
|
77
|
+
refresh! unless @prevent_refresh
|
78
|
+
end
|
79
|
+
|
80
|
+
def prevent_refresh!
|
81
|
+
@prevent_refresh = true
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def component_class
|
87
|
+
self.class.component_class
|
88
|
+
end
|
89
|
+
|
90
|
+
def stimulus_controller
|
91
|
+
component_class.stimulus_controller
|
92
|
+
end
|
93
|
+
|
94
|
+
def component
|
95
|
+
return @component if @component
|
96
|
+
@component = component_class.allocate
|
97
|
+
reflex = self
|
98
|
+
exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!, :selector]
|
99
|
+
exposed_methods.each do |meth|
|
100
|
+
@component.define_singleton_method(meth) do |*a|
|
101
|
+
reflex.send(meth, *a)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
@component
|
105
|
+
end
|
106
|
+
|
107
|
+
def set_state(new_state = {})
|
108
|
+
ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
|
109
|
+
end
|
110
|
+
|
111
|
+
def state
|
112
|
+
ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
|
113
|
+
end
|
114
|
+
|
115
|
+
def save_state
|
116
|
+
new_state = {}
|
117
|
+
component.safe_instance_variables.each do |k|
|
118
|
+
new_state[k] = component.instance_variable_get(k)
|
119
|
+
end
|
120
|
+
set_state(new_state)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
|
-
require "view_component_reflex/state_adapter/session"
|
2
|
-
require "view_component_reflex/
|
3
|
-
require
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
require "view_component_reflex/state_adapter/session"
|
2
|
+
require "view_component_reflex/state_adapter/memory"
|
3
|
+
require "view_component_reflex/engine"
|
4
|
+
require "stimulus_reflex"
|
5
|
+
|
6
|
+
module ViewComponentReflex
|
7
|
+
# Your code goes here...
|
8
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
|
-
module ViewComponentReflex
|
2
|
-
class Engine < ::Rails::Engine
|
3
|
-
class << self
|
4
|
-
mattr_accessor :state_adapter
|
5
|
-
|
6
|
-
self.state_adapter = StateAdapter::
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.configure
|
10
|
-
yield self if block_given?
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
1
|
+
module ViewComponentReflex
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
class << self
|
4
|
+
mattr_accessor :state_adapter
|
5
|
+
|
6
|
+
self.state_adapter = StateAdapter::Memory
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.configure
|
10
|
+
yield self if block_given?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
VIEW_COMPONENT_REFLEX_MEMORY_STATE = {}
|
2
|
+
module ViewComponentReflex
|
3
|
+
module StateAdapter
|
4
|
+
class Memory
|
5
|
+
def self.state(request, key)
|
6
|
+
VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s] ||= {}
|
7
|
+
VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s][key] ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.set_state(request, _, key, new_state)
|
11
|
+
new_state.each do |k, v|
|
12
|
+
state(request, key)[k] = v
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.store_state(request, key, new_state = {})
|
17
|
+
VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s] ||= {}
|
18
|
+
VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s][key] ||= {}
|
19
|
+
new_state.each do |k, v|
|
20
|
+
VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s][key][k] = v
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,3 +1,3 @@
|
|
1
|
-
module ViewComponentReflex
|
2
|
-
VERSION = '
|
3
|
-
end
|
1
|
+
module ViewComponentReflex
|
2
|
+
VERSION = '2.0.0'
|
3
|
+
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:
|
4
|
+
version: 2.0.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-
|
11
|
+
date: 2020-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -69,8 +69,10 @@ 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
|
75
|
+
- lib/view_component_reflex/state_adapter/memory.rb
|
74
76
|
- lib/view_component_reflex/state_adapter/session.rb
|
75
77
|
- lib/view_component_reflex/version.rb
|
76
78
|
homepage: https://github.com/joshleblanc/view_component_reflex
|