view_component_reflex 3.1.10 → 3.1.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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