view_component_reflex 3.1.10 → 3.1.11

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.
@@ -1,243 +1,243 @@
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
- :@_content_evaluated, :@_render_in_block
206
- ]
207
- end
208
-
209
- def content
210
- if cached_content && !@_render_in_block
211
- cached_content
212
- else
213
- super
214
- end
215
- end
216
-
217
- def cached_content
218
- @__cached_content__
219
- end
220
-
221
- def create_safe_state
222
- new_state = {}
223
-
224
- # this will almost certainly break
225
- safe_instance_variables.each do |k|
226
- new_state[k] = instance_variable_get(k)
227
- end
228
-
229
- new_state[:@__cached_content__] = content
230
-
231
- new_state
232
- end
233
-
234
- def merge_data_attributes(options, attributes)
235
- data = options[:data]
236
- if data.nil?
237
- options[:data] = attributes
238
- else
239
- options[:data].merge! attributes
240
- end
241
- end
242
- end
243
- 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
+ :@_content_evaluated, :@_render_in_block
206
+ ]
207
+ end
208
+
209
+ def content
210
+ if cached_content && !@_render_in_block
211
+ cached_content
212
+ else
213
+ super
214
+ end
215
+ end
216
+
217
+ def cached_content
218
+ @__cached_content__
219
+ end
220
+
221
+ def create_safe_state
222
+ new_state = {}
223
+
224
+ # this will almost certainly break
225
+ safe_instance_variables.each do |k|
226
+ new_state[k] = instance_variable_get(k)
227
+ end
228
+
229
+ new_state[:@__cached_content__] = content
230
+
231
+ new_state
232
+ end
233
+
234
+ def merge_data_attributes(options, attributes)
235
+ data = options[:data]
236
+ if data.nil?
237
+ options[:data] = attributes
238
+ else
239
+ options[:data].merge! attributes
240
+ end
241
+ end
242
+ end
243
+ end