view_component_reflex 0.6.3 → 1.2.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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ed2f845769bba6d5142bef6d6fc601b44841080c49ee47abc0036801f62b145
|
4
|
+
data.tar.gz: 73e625d1948e5b477def8e098a4a4d55b9a02763c575e1d4bdd4c734e0f208b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76c5b53b3acb0fa68084b762ad14fddbaa0aee1d0b9bbe67f33910f088e7b60062bf5dcf61c7703932b2cfa228f4bccfc768e6b9ab64253397e3483efd50cfb5
|
7
|
+
data.tar.gz: 077a34955920288651b183d77971d30eb4fbfeabe45aaa799d78f2ea242cb46f328d23223508d053e53210c00e9aeb570e9d234b571aba3005e57b6448afd5fc
|
data/README.md
CHANGED
@@ -6,53 +6,60 @@ ViewComponentReflex allows you to write reflexes right in your view component co
|
|
6
6
|
|
7
7
|
You can add reflexes to your component by adding inheriting from `ViewComponentReflex::Component`.
|
8
8
|
|
9
|
-
To add a reflex to your component, use the `reflex` method.
|
10
|
-
|
11
|
-
```ruby
|
12
|
-
reflex :my_cool_reflex do
|
13
|
-
# do stuff
|
14
|
-
refresh!
|
15
|
-
end
|
16
|
-
```
|
17
|
-
|
18
9
|
This will act as if you created a reflex with the method `my_cool_stuff`. To call this reflex, add `data-reflex="click->MyComponentReflex#my_cool_reflex"`, just like you're
|
19
10
|
using stimulus reflex.
|
20
11
|
|
21
|
-
|
22
|
-
|
23
|
-
In addition to calling reflexes, there is a rudimentary state system. You can initialize component-local state with `initialize_state(obj)`, where `obj` is a hash.
|
24
|
-
|
25
|
-
You can access state with the `state` helper. See the code below for an example. Calling `set_state` will set the state,
|
26
|
-
and also re-render your component.
|
27
|
-
|
28
|
-
If you're using state add `data-key="<%= key %>"` to any html element using a reflex. This
|
29
|
-
lets ViewComponentReflex keep track of which state belongs to which component.
|
30
|
-
|
12
|
+
ViewComponentReflex will maintain your component's instance variables between renders. You need to include `data-key=<%= key %>` on your root element, as well
|
13
|
+
as any element that stimulates a reflex. ViewComponent is inherently state-less, so the key is used to reconcile state to its respective component.
|
31
14
|
|
15
|
+
### Example
|
32
16
|
```ruby
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
set_state(count: state[:count] + 1)
|
44
|
-
end
|
45
|
-
end
|
17
|
+
# counter_component.rb
|
18
|
+
class CounterComponent < ViewComponentReflex::Component
|
19
|
+
def initialize
|
20
|
+
@count = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def increment
|
24
|
+
@count += 1
|
25
|
+
end
|
26
|
+
end
|
46
27
|
```
|
47
28
|
|
48
29
|
```erb
|
49
30
|
# counter_component.html.erb
|
50
|
-
|
51
|
-
<p><%=
|
31
|
+
<%= component_controller do %>
|
32
|
+
<p><%= @count %></p>
|
52
33
|
<button type="button" data-reflex="click->CounterComponentReflex#increment" data-key="<%= key %>">Click</button>
|
53
|
-
|
34
|
+
<% end %>
|
54
35
|
```
|
55
36
|
|
37
|
+
## Collections
|
38
|
+
|
39
|
+
In order to reconcile state to components in collections, you can specify a `collection_key` method that returns some
|
40
|
+
value unique to that component.
|
41
|
+
|
42
|
+
```
|
43
|
+
class TodoComponent < ViewComponentReflex::Component
|
44
|
+
def initialize(todo:)
|
45
|
+
@todo = todo
|
46
|
+
end
|
47
|
+
|
48
|
+
def collection_key
|
49
|
+
@todo.id
|
50
|
+
end
|
51
|
+
end
|
52
|
+
#
|
53
|
+
<%= render(TodoComponent.with_collection(Todo.all)) %>
|
54
|
+
```
|
55
|
+
|
56
|
+
## API
|
57
|
+
|
58
|
+
### permit_parameter?(initial_param, new_params)
|
59
|
+
If a new parameter is passed to the component during rendering, it is used instead of what's in state.
|
60
|
+
If you're storing instances in state, you can use this to properly compare them.
|
61
|
+
|
62
|
+
|
56
63
|
## Custom State Adapters
|
57
64
|
|
58
65
|
ViewComponentReflex uses session for its state by default. To change this, add
|
@@ -76,19 +83,27 @@ class YourAdapter
|
|
76
83
|
end
|
77
84
|
|
78
85
|
##
|
79
|
-
#
|
86
|
+
# set_state is used to modify the state.
|
87
|
+
#
|
88
|
+
# request - a rails request object
|
89
|
+
# controller - the current controller
|
80
90
|
# key - a unique string that identifies the component
|
81
91
|
# new_state - the new state to set
|
82
|
-
def self.set_state(
|
92
|
+
def self.set_state(request, controller, key, new_state)
|
93
|
+
# update the state
|
83
94
|
end
|
84
95
|
|
85
96
|
|
86
97
|
##
|
98
|
+
# store_state is used to replace the state entirely. It only accepts
|
99
|
+
# a request object, rather than a reflex because it's called from the component's
|
100
|
+
# side with the component's instance variables.
|
101
|
+
#
|
87
102
|
# request - a rails request object
|
88
103
|
# key - a unique string that identifies the component instance
|
89
104
|
# new_state - a hash containing the component state
|
90
105
|
def self.store_state(request, key, new_state = {})
|
91
|
-
#
|
106
|
+
# replace the state
|
92
107
|
end
|
93
108
|
end
|
94
109
|
```
|
@@ -1,84 +1,159 @@
|
|
1
1
|
module ViewComponentReflex
|
2
2
|
class Component < ViewComponent::Base
|
3
3
|
class << self
|
4
|
-
def
|
5
|
-
stimulus_reflex.reflex(name, &blk)
|
6
|
-
end
|
7
|
-
|
8
|
-
def stimulus_reflex
|
4
|
+
def init_stimulus_reflex
|
9
5
|
klass = self
|
10
6
|
@stimulus_reflex ||= Object.const_set(name + "Reflex", Class.new(StimulusReflex::Reflex) {
|
11
|
-
def
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
def refresh!(primary_selector = "[data-controller=\"#{stimulus_controller}\"]", *selectors)
|
7
|
+
def refresh!(primary_selector = "[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]", *selectors)
|
8
|
+
save_state
|
16
9
|
@channel.send :render_page_and_broadcast_morph, self, [primary_selector, *selectors], {
|
17
|
-
dataset
|
18
|
-
args
|
19
|
-
attrs
|
20
|
-
selectors
|
21
|
-
target
|
22
|
-
url
|
23
|
-
|
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"
|
24
17
|
}
|
25
18
|
end
|
26
19
|
|
27
20
|
def refresh_all!
|
28
|
-
refresh!(
|
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
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
|
33
|
-
refresh!(primary_selector, *selectors)
|
31
|
+
def respond_to_missing?(name, _ = false)
|
32
|
+
!!name.to_proc
|
34
33
|
end
|
35
34
|
|
36
|
-
before_reflex do |
|
37
|
-
|
35
|
+
before_reflex do |a|
|
36
|
+
a.send a.method_name
|
38
37
|
throw :abort
|
39
38
|
end
|
40
39
|
|
41
|
-
def
|
42
|
-
|
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!
|
47
|
+
end
|
48
|
+
|
49
|
+
define_method :component_class do
|
50
|
+
@component_class ||= klass
|
43
51
|
end
|
44
52
|
|
45
|
-
|
46
|
-
|
53
|
+
private :component_class
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def stimulus_controller
|
58
|
+
component_class.stimulus_controller
|
47
59
|
end
|
48
60
|
|
49
|
-
|
50
|
-
|
51
|
-
|
61
|
+
def component
|
62
|
+
return @component if @component
|
63
|
+
@component = component_class.allocate
|
64
|
+
reflex = self
|
65
|
+
exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller]
|
66
|
+
exposed_methods.each do |meth|
|
67
|
+
@component.define_singleton_method(meth) do |*a|
|
68
|
+
reflex.send(meth, *a)
|
69
|
+
end
|
52
70
|
end
|
71
|
+
@component
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_state(new_state = {})
|
75
|
+
ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
|
76
|
+
end
|
77
|
+
|
78
|
+
def state
|
79
|
+
ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
|
80
|
+
end
|
81
|
+
|
82
|
+
def save_state
|
83
|
+
new_state = {}
|
84
|
+
component.instance_variables.each do |k|
|
85
|
+
new_state[k] = component.instance_variable_get(k)
|
86
|
+
end
|
87
|
+
set_state(new_state)
|
53
88
|
end
|
54
89
|
})
|
55
90
|
end
|
56
91
|
end
|
57
92
|
|
58
|
-
def
|
59
|
-
|
93
|
+
def self.stimulus_controller
|
94
|
+
name.chomp("Component").underscore.dasherize
|
60
95
|
end
|
61
96
|
|
62
97
|
def stimulus_reflex?
|
63
98
|
helpers.controller.instance_variable_get(:@stimulus_reflex)
|
64
99
|
end
|
65
100
|
|
101
|
+
def component_controller(opts = {}, &blk)
|
102
|
+
self.class.init_stimulus_reflex
|
103
|
+
init_key
|
104
|
+
opts[:data] = {
|
105
|
+
controller: self.class.stimulus_controller,
|
106
|
+
key: key,
|
107
|
+
**(opts[:data] || {})
|
108
|
+
}
|
109
|
+
content_tag :div, capture(&blk), opts
|
110
|
+
end
|
111
|
+
|
66
112
|
# key is required if you're using state
|
67
113
|
# We can't initialize the session state in the initial method
|
68
114
|
# because it doesn't have a view_context yet
|
69
115
|
# This is the next best place to do it
|
70
|
-
def
|
71
|
-
|
116
|
+
def init_key
|
117
|
+
# we want the erb file that renders the component. `caller` gives the file name,
|
118
|
+
# and line number, which should be unique. We hash it to make it a nice number
|
119
|
+
key = caller.select { |p| p.include? ".html.erb" }[1]&.hash.to_s
|
120
|
+
key += collection_key.to_s if collection_key
|
121
|
+
@key = key
|
122
|
+
end
|
72
123
|
|
124
|
+
def collection_key
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def permit_parameter?(initial_param, new_param)
|
129
|
+
initial_param != new_param
|
130
|
+
end
|
131
|
+
|
132
|
+
def key
|
73
133
|
# initialize session state
|
74
134
|
if !stimulus_reflex? || session[@key].nil?
|
75
|
-
|
135
|
+
new_state = {}
|
136
|
+
|
137
|
+
# this will almost certainly break
|
138
|
+
blacklist = [
|
139
|
+
:@view_context, :@lookup_context, :@view_renderer, :@view_flow,
|
140
|
+
:@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
|
141
|
+
:@helpers, :@controller, :@request, :@content
|
142
|
+
]
|
143
|
+
instance_variables.reject { |k| blacklist.include?(k) }.each do |k|
|
144
|
+
new_state[k] = instance_variable_get(k)
|
145
|
+
end
|
146
|
+
ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
|
147
|
+
ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", new_state)
|
148
|
+
else
|
149
|
+
initial_state = ViewComponentReflex::Engine.state_adapter.state(request, "#{@key}_initial")
|
150
|
+
ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
|
151
|
+
unless permit_parameter?(initial_state[k], instance_variable_get(k))
|
152
|
+
instance_variable_set(k, v)
|
153
|
+
end
|
154
|
+
end
|
76
155
|
end
|
77
156
|
@key
|
78
157
|
end
|
79
|
-
|
80
|
-
def state
|
81
|
-
ViewComponentReflex::Engine.state_adapter.state(request, key)
|
82
|
-
end
|
83
158
|
end
|
84
159
|
end
|
@@ -5,12 +5,12 @@ module ViewComponentReflex
|
|
5
5
|
request.session[key] ||= {}
|
6
6
|
end
|
7
7
|
|
8
|
-
def self.set_state(
|
8
|
+
def self.set_state(request, controller, key, new_state)
|
9
9
|
new_state.each do |k, v|
|
10
|
-
state(
|
10
|
+
state(request, key)[k] = v
|
11
11
|
end
|
12
|
-
store =
|
13
|
-
store.commit_session
|
12
|
+
store = request.session.instance_variable_get("@by")
|
13
|
+
store.commit_session request, controller.response
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.store_state(request, key, new_state = {})
|
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: 1.2.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-06-
|
11
|
+
date: 2020-06-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|