view_component_reflex 3.1.2 → 3.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,211 +1,228 @@
1
- module ViewComponentReflex
2
- class Component < ViewComponent::Base
3
- class_attribute :reflex_base_class, default: ViewComponentReflex::Reflex
4
-
5
- class << self
6
- def init_stimulus_reflex
7
- factory = ViewComponentReflex::ReflexFactory.new(self)
8
- @stimulus_reflex ||= factory.reflex
9
- wire_up_callbacks if factory.new?
10
- end
11
-
12
- def queue_callback(key, args, blk)
13
- callbacks(key).push({
14
- args: args,
15
- blk: blk
16
- })
17
- end
18
-
19
- def callbacks(key)
20
- @callbacks ||= {}
21
- @callbacks[key] ||= []
22
- end
23
-
24
- def register_callbacks(key)
25
- callbacks(key).each do |cb|
26
- @stimulus_reflex.send("#{key}_reflex", *cb[:args], &cb[:blk])
27
- end
28
- end
29
-
30
- def before_reflex(*args, &blk)
31
- queue_callback(:before, args, blk)
32
- end
33
-
34
- def after_reflex(*args, &blk)
35
- queue_callback(:after, args, blk)
36
- end
37
-
38
- def around_reflex(*args, &blk)
39
- queue_callback(:around, args, blk)
40
- end
41
-
42
- def wire_up_callbacks
43
- register_callbacks(:before)
44
- register_callbacks(:after)
45
- register_callbacks(:around)
46
- end
47
- end
48
-
49
- def self.stimulus_controller
50
- name.chomp("Component").underscore.dasherize.gsub("/", "--")
51
- end
52
-
53
- def stimulus_reflex?
54
- helpers.controller.instance_variable_get(:@stimulus_reflex)
55
- end
56
-
57
- def component_controller(opts_or_tag = :div, opts = {}, &blk)
58
- init_key
59
-
60
- tag = :di
61
- options = if opts_or_tag.is_a? Hash
62
- opts_or_tag
63
- else
64
- tag = opts_or_tag
65
- opts
66
- end
67
-
68
- options[:data] = {
69
- controller: self.class.stimulus_controller,
70
- key: key,
71
- **(options[:data] || {})
72
- }
73
- content_tag tag, capture(&blk), options
74
- end
75
-
76
- def can_render_to_string?
77
- omitted_from_state.empty?
78
- end
79
-
80
- # key is required if you're using state
81
- # We can't initialize the session state in the initial method
82
- # because it doesn't have a view_context yet
83
- # This is the next best place to do it
84
- def init_key
85
- # we want the erb file that renders the component. `caller` gives the file name,
86
- # and line number, which should be unique. We hash it to make it a nice number
87
- erb_file = caller.select { |p| p.match? /.\.html\.(haml|erb|slim)/ }[1]
88
- key = if erb_file
89
- Digest::SHA2.hexdigest(erb_file.split(":in")[0])
90
- else
91
- ""
92
- end
93
- key += collection_key.to_s if collection_key
94
- @key = key
95
- end
96
-
97
- # Helper to use to create the proper reflex data attributes for an element
98
- def reflex_data_attributes(reflex)
99
- action, method = reflex.to_s.split("->")
100
- if method.nil?
101
- method = action
102
- action = "click"
103
- end
104
-
105
- {
106
- reflex: "#{action}->#{self.class.name}##{method}",
107
- key: key
108
- }
109
- end
110
-
111
- def reflex_tag(reflex, name, content_or_options_with_block = {}, options = {}, escape = true, &block)
112
- if content_or_options_with_block.is_a?(Hash)
113
- merge_data_attributes(content_or_options_with_block, reflex_data_attributes(reflex))
114
- else
115
- merge_data_attributes(options, reflex_data_attributes(reflex))
116
- end
117
- content_tag(name, content_or_options_with_block, options, escape, &block)
118
- end
119
-
120
- def collection_key
121
- nil
122
- end
123
-
124
- def permit_parameter?(initial_param, new_param)
125
- initial_param != new_param
126
- end
127
-
128
- def omitted_from_state
129
- []
130
- end
131
-
132
- def after_state_initialized(parameters_changed)
133
- # called after state component has been hydrated
134
- end
135
-
136
- # def receive_params(old_state, params)
137
- # # no op
138
- # end
139
-
140
- def key
141
- adapter = ViewComponentReflex::Engine.state_adapter
142
-
143
- # initialize session state
144
- if (!stimulus_reflex? || adapter.state(request, @key).empty?) && !@initialized_state
145
-
146
- new_state = create_safe_state
147
-
148
- adapter.wrap_write_async do
149
- adapter.store_state(request, @key, new_state)
150
- adapter.store_state(request, "#{@key}_initial", new_state)
151
- end
152
- elsif !@initialized_state
153
- initial_state = adapter.state(request, "#{@key}_initial")
154
-
155
- # incoming_params = safe_instance_variables.each_with_object({}) { |var, obj| obj[var] = instance_variable_get(var) }
156
- # receive_params(ViewComponentReflex::Engine.state_adapter.state(request, @key), incoming_params)
157
-
158
- parameters_changed = []
159
- adapter.state(request, @key).each do |k, v|
160
- instance_value = instance_variable_get(k)
161
- if permit_parameter?(initial_state[k], instance_value)
162
- parameters_changed << k
163
- adapter.wrap_write_async do
164
- adapter.set_state(request, controller, "#{@key}_initial", {k => instance_value})
165
- adapter.set_state(request, controller, @key, {k => instance_value})
166
- end
167
- else
168
- instance_variable_set(k, v)
169
- end
170
- end
171
- after_state_initialized(parameters_changed)
172
- end
173
-
174
- @initialized_state = true
175
- @key
176
- end
177
-
178
- def safe_instance_variables
179
- instance_variables - unsafe_instance_variables - omitted_from_state
180
- end
181
-
182
- private
183
-
184
- def unsafe_instance_variables
185
- [
186
- :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
187
- :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
188
- :@helpers, :@controller, :@request, :@tag_builder, :@initialized_state
189
- ]
190
- end
191
-
192
- def create_safe_state
193
- new_state = {}
194
-
195
- # this will almost certainly break
196
- safe_instance_variables.each do |k|
197
- new_state[k] = instance_variable_get(k)
198
- end
199
- new_state
200
- end
201
-
202
- def merge_data_attributes(options, attributes)
203
- data = options[:data]
204
- if data.nil?
205
- options[:data] = attributes
206
- else
207
- options[:data].merge! attributes
208
- end
209
- end
210
- end
211
- end
1
+ module ViewComponentReflex
2
+ class Component < ViewComponent::Base
3
+ class_attribute :reflex_base_class, default: ViewComponentReflex::Reflex
4
+ attr_reader :key
5
+
6
+ class << self
7
+ def init_stimulus_reflex
8
+ factory = ViewComponentReflex::ReflexFactory.new(self)
9
+ @stimulus_reflex ||= factory.reflex
10
+ wire_up_callbacks if factory.new?
11
+ end
12
+
13
+ def queue_callback(key, args, blk)
14
+ callbacks(key).push({
15
+ args: args,
16
+ blk: blk
17
+ })
18
+ end
19
+
20
+ def callbacks(key)
21
+ @callbacks ||= {}
22
+ @callbacks[key] ||= []
23
+ end
24
+
25
+ def register_callbacks(key)
26
+ callbacks(key).each do |cb|
27
+ @stimulus_reflex.send("#{key}_reflex", *cb[:args], &cb[:blk])
28
+ end
29
+ end
30
+
31
+ def before_reflex(*args, &blk)
32
+ queue_callback(:before, args, blk)
33
+ end
34
+
35
+ def after_reflex(*args, &blk)
36
+ queue_callback(:after, args, blk)
37
+ end
38
+
39
+ def around_reflex(*args, &blk)
40
+ queue_callback(:around, args, blk)
41
+ end
42
+
43
+ def wire_up_callbacks
44
+ register_callbacks(:before)
45
+ register_callbacks(:after)
46
+ register_callbacks(:around)
47
+ end
48
+ end
49
+
50
+ def self.stimulus_controller
51
+ name.chomp("Component").underscore.dasherize.gsub("/", "--")
52
+ end
53
+
54
+ def stimulus_reflex?
55
+ helpers.controller.instance_variable_get(:@stimulus_reflex)
56
+ end
57
+
58
+ def component_controller(opts_or_tag = :div, opts = {}, &blk)
59
+ initialize_component
60
+
61
+ tag = :div
62
+ options = if opts_or_tag.is_a? Hash
63
+ opts_or_tag
64
+ else
65
+ tag = opts_or_tag
66
+ opts
67
+ end
68
+
69
+ options[:data] = {
70
+ controller: self.class.stimulus_controller,
71
+ key: key,
72
+ **(options[:data] || {})
73
+ }
74
+ content_tag tag, capture(&blk), options
75
+ end
76
+
77
+ def can_render_to_string?
78
+ omitted_from_state.empty?
79
+ end
80
+
81
+ # We can't truly initialize the component without the view_context,
82
+ # which isn't available in the `initialize` method. We require the
83
+ # developer to wrap components in `component_controller`, so this is where
84
+ # we truly initialize the component.
85
+ # This method is overridden in reflex.rb when the component is re-rendered. The
86
+ # override simply sets @key to element.dataset[:key]
87
+ # We don't want it to initialize the state again, and since we're rendering the component
88
+ # outside of the view, we need to skip the initialize_key method as well
89
+ def initialize_component
90
+ initialize_key
91
+ initialize_state
92
+ end
93
+
94
+ # Note to self:
95
+ # This has to be in the Component class because there are situations
96
+ # where the controller is the one rendering the component
97
+ # so we can't rely on the component created by the reflex
98
+ def initialize_state
99
+ return if state_initialized?
100
+ adapter = ViewComponentReflex::Engine.state_adapter
101
+
102
+ # newly mounted
103
+ if !stimulus_reflex? || adapter.state(request, @key).empty?
104
+
105
+ new_state = create_safe_state
106
+
107
+ adapter.wrap_write_async do
108
+ adapter.store_state(request, @key, new_state)
109
+ adapter.store_state(request, "#{@key}_initial", new_state)
110
+ end
111
+
112
+ # updating a mounted component
113
+ else
114
+ initial_state = adapter.state(request, "#{@key}_initial")
115
+
116
+ parameters_changed = []
117
+ adapter.state(request, @key).each do |k, v|
118
+ instance_value = instance_variable_get(k)
119
+ if permit_parameter?(initial_state[k], instance_value)
120
+ parameters_changed << k
121
+ adapter.wrap_write_async do
122
+ adapter.set_state(request, controller, "#{@key}_initial", {k => instance_value})
123
+ adapter.set_state(request, controller, @key, {k => instance_value})
124
+ end
125
+ else
126
+ instance_variable_set(k, v)
127
+ end
128
+ end
129
+ after_state_initialized(parameters_changed)
130
+ end
131
+ @state_initialized = true
132
+ end
133
+
134
+ def state_initialized?
135
+ @state_initialized
136
+ end
137
+
138
+ def initialize_key
139
+ # we want the erb file that renders the component. `caller` gives the file name,
140
+ # and line number, which should be unique. We hash it to make it a nice number
141
+ erb_file = caller.select { |p| p.match? /.\.html\.(haml|erb|slim)/ }[1]
142
+ key = if erb_file
143
+ Digest::SHA2.hexdigest(erb_file.split(":in")[0])
144
+ else
145
+ ""
146
+ end
147
+ key += collection_key.to_s if collection_key
148
+ @key = key
149
+ end
150
+
151
+ # Helper to use to create the proper reflex data attributes for an element
152
+ def reflex_data_attributes(reflex)
153
+ action, method = reflex.to_s.split("->")
154
+ if method.nil?
155
+ method = action
156
+ action = "click"
157
+ end
158
+
159
+ {
160
+ reflex: "#{action}->#{self.class.name}##{method}",
161
+ key: key
162
+ }
163
+ end
164
+
165
+ def reflex_tag(reflex, name, content_or_options_with_block = {}, options = {}, escape = true, &block)
166
+ if content_or_options_with_block.is_a?(Hash)
167
+ merge_data_attributes(content_or_options_with_block, reflex_data_attributes(reflex))
168
+ else
169
+ merge_data_attributes(options, reflex_data_attributes(reflex))
170
+ end
171
+ content_tag(name, content_or_options_with_block, options, escape, &block)
172
+ end
173
+
174
+ def collection_key
175
+ nil
176
+ end
177
+
178
+ def permit_parameter?(initial_param, new_param)
179
+ initial_param != new_param
180
+ end
181
+
182
+ def omitted_from_state
183
+ []
184
+ end
185
+
186
+ def after_state_initialized(parameters_changed)
187
+ # called after state component has been hydrated
188
+ end
189
+
190
+ # def receive_params(old_state, params)
191
+ # # no op
192
+ # end
193
+
194
+ def safe_instance_variables
195
+ instance_variables - unsafe_instance_variables - omitted_from_state
196
+ end
197
+
198
+ private
199
+
200
+ def unsafe_instance_variables
201
+ [
202
+ :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
203
+ :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
204
+ :@helpers, :@controller, :@request, :@tag_builder, :@state_initialized
205
+ ]
206
+ end
207
+
208
+ def create_safe_state
209
+ new_state = {}
210
+
211
+ # this will almost certainly break
212
+ safe_instance_variables.each do |k|
213
+ new_state[k] = instance_variable_get(k)
214
+ end
215
+
216
+ new_state
217
+ end
218
+
219
+ def merge_data_attributes(options, attributes)
220
+ data = options[:data]
221
+ if data.nil?
222
+ options[:data] = attributes
223
+ else
224
+ options[:data].merge! attributes
225
+ end
226
+ end
227
+ end
228
+ end
@@ -1,12 +1,12 @@
1
- require "stimulus_reflex"
2
- require 'view_component_reflex/reflex_factory'
3
- require "view_component_reflex/state_adapter/base"
4
- require "view_component_reflex/state_adapter/session"
5
- require "view_component_reflex/state_adapter/memory"
6
- require "view_component_reflex/state_adapter/redis"
7
- require "view_component_reflex/reflex"
8
- require "view_component_reflex/engine"
9
-
10
- module ViewComponentReflex
11
- # Your code goes here...
12
- end
1
+ require "stimulus_reflex"
2
+ require 'view_component_reflex/reflex_factory'
3
+ require "view_component_reflex/state_adapter/base"
4
+ require "view_component_reflex/state_adapter/session"
5
+ require "view_component_reflex/state_adapter/memory"
6
+ require "view_component_reflex/state_adapter/redis"
7
+ require "view_component_reflex/reflex"
8
+ require "view_component_reflex/engine"
9
+
10
+ module ViewComponentReflex
11
+ # Your code goes here...
12
+ end