view_component_reflex 3.3.4 → 3.3.6

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.
data/Rakefile CHANGED
@@ -1,32 +1,32 @@
1
- begin
2
- require 'bundler/setup'
3
- rescue LoadError
4
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
- end
6
-
7
- require 'rdoc/task'
8
-
9
- RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'ViewComponentReflex'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.md')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
15
- end
16
-
17
- # APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
- # load 'rails/tasks/engine.rake'
19
-
20
- load 'rails/tasks/statistics.rake'
21
-
22
- require 'bundler/gem_tasks'
23
-
24
- require 'rake/testtask'
25
-
26
- Rake::TestTask.new(:test) do |t|
27
- t.libs << 'test'
28
- t.pattern = 'test/**/*_test.rb'
29
- t.verbose = false
30
- end
31
-
32
- task default: :test
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ViewComponentReflex'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ # APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ # load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -1,293 +1,293 @@
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
- attr_reader :_state_adapter
8
-
9
- def init_stimulus_reflex
10
- factory = ViewComponentReflex::ReflexFactory.new(self)
11
- @stimulus_reflex ||= factory.reflex
12
-
13
- # Always wire up new callbacks in development
14
- if Rails.env.development?
15
- reset_callbacks
16
- wire_up_callbacks
17
- elsif factory.new? # only wire up callbacks in production if they haven't been wired up yet
18
- wire_up_callbacks
19
- end
20
- end
21
-
22
- def queue_callback(key, args, blk)
23
- callbacks(key).push({
24
- args: args,
25
- blk: blk
26
- })
27
- end
28
-
29
- def callbacks(key)
30
- @@callbacks ||= {}
31
- @@callbacks[key] ||= []
32
- end
33
-
34
- def register_callbacks(key)
35
- callbacks(key).each do |cb|
36
- @stimulus_reflex.send("#{key}_reflex", *cb[:args], &cb[:blk])
37
- end
38
- end
39
-
40
- def before_reflex(*args, &blk)
41
- queue_callback(:before, args, blk)
42
- end
43
-
44
- def after_reflex(*args, &blk)
45
- queue_callback(:after, args, blk)
46
- end
47
-
48
- def around_reflex(*args, &blk)
49
- queue_callback(:around, args, blk)
50
- end
51
-
52
- def reset_callbacks
53
- # SR uses :process as the underlying callback key
54
- @stimulus_reflex.reset_callbacks(:process)
55
- end
56
-
57
- def wire_up_callbacks
58
- register_callbacks(:before)
59
- register_callbacks(:after)
60
- register_callbacks(:around)
61
- end
62
-
63
- def state_adapter(what)
64
- if what.is_a?(Symbol) || what.is_a?(String)
65
- class_name = what.to_s.camelize
66
- @_state_adapter = StateAdapter.const_get class_name
67
- else
68
- @_state_adapter = what
69
- end
70
- end
71
- end
72
-
73
- def self.stimulus_controller
74
- name.chomp("Component").underscore.dasherize.gsub("/", "--")
75
- end
76
-
77
- def stimulus_reflex?
78
- helpers.controller.instance_variable_get(:@stimulus_reflex)
79
- end
80
-
81
- def before_render
82
- adapter.extend_component(self)
83
- end
84
-
85
- def component_controller(opts_or_tag = :div, opts = {}, &blk)
86
- initialize_component
87
-
88
- tag = :div
89
- options = if opts_or_tag.is_a? Hash
90
- opts_or_tag
91
- else
92
- tag = opts_or_tag
93
- opts
94
- end
95
-
96
- options[:data] = {
97
- controller: self.class.stimulus_controller,
98
- key: key,
99
- **(options[:data] || {})
100
- }
101
- content_tag tag, capture(&blk), options
102
- end
103
-
104
- def can_render_to_string?
105
- omitted_from_state.empty?
106
- end
107
-
108
- # We can't truly initialize the component without the view_context,
109
- # which isn't available in the `initialize` method. We require the
110
- # developer to wrap components in `component_controller`, so this is where
111
- # we truly initialize the component.
112
- # This method is overridden in reflex.rb when the component is re-rendered. The
113
- # override simply sets @key to element.dataset[:key]
114
- # We don't want it to initialize the state again, and since we're rendering the component
115
- # outside of the view, we need to skip the initialize_key method as well
116
- def initialize_component
117
- initialize_key
118
- initialize_state
119
- end
120
-
121
- # Note to self:
122
- # This has to be in the Component class because there are situations
123
- # where the controller is the one rendering the component
124
- # so we can't rely on the component created by the reflex
125
- def initialize_state
126
- return if state_initialized?
127
-
128
- adapter.extend_component(self)
129
-
130
- # newly mounted
131
- if !stimulus_reflex? || state(@key).empty?
132
-
133
- new_state = create_safe_state
134
-
135
- wrap_write_async do
136
- store_state(@key, new_state)
137
- store_state("#{@key}_initial", new_state)
138
- end
139
-
140
- # updating a mounted component
141
- else
142
- initial_state = state("#{@key}_initial")
143
-
144
- parameters_changed = []
145
- state(@key).each do |k, v|
146
- instance_value = instance_variable_get(k)
147
- if permit_parameter?(initial_state[k], instance_value)
148
- parameters_changed << k
149
- wrap_write_async do
150
- set_state("#{@key}_initial", { k => instance_value })
151
- set_state(@key, { k => instance_value })
152
- end
153
- else
154
- instance_variable_set(k, v)
155
- end
156
- end
157
- after_state_initialized(parameters_changed)
158
- end
159
- @state_initialized = true
160
- end
161
-
162
- def adapter
163
- self.class._state_adapter || ViewComponentReflex::Engine.state_adapter
164
- end
165
-
166
- def wrap_write_async(&blk)
167
- adapter.wrap_write_async(&blk)
168
- end
169
-
170
- def set_state(key, new_state)
171
- adapter.set_state(request, controller, key, new_state)
172
- end
173
-
174
- def state(key)
175
- adapter.state(request, key)
176
- end
177
-
178
- def store_state(key, new_state = {})
179
- adapter.store_state(request, key, new_state)
180
- end
181
-
182
- def state_initialized?
183
- @state_initialized
184
- end
185
-
186
- def initialize_key
187
- # we want the erb file that renders the component. `caller` gives the file name,
188
- # and line number, which should be unique. We hash it to make it a nice number
189
- erb_file = caller.select { |p| p.match? /.\.html\.(haml|erb|slim)/ }[1]
190
- key = if erb_file
191
- erb_file.split(":in")[0]
192
-
193
- else
194
- ""
195
- end
196
- key += collection_key.to_s if collection_key
197
- @key = Digest::SHA2.hexdigest(key)
198
- end
199
-
200
- # Helper to use to create the proper reflex data attributes for an element
201
- def reflex_data_attributes(reflex)
202
- action, method = reflex.to_s.split("->")
203
- if method.nil?
204
- method = action
205
- action = "click"
206
- end
207
-
208
- {
209
- reflex: "#{action}->#{self.class.name}##{method}",
210
- key: key
211
- }
212
- end
213
-
214
- def reflex_tag(reflex, name, content_or_options_with_block = {}, options = {}, escape = true, &block)
215
- if content_or_options_with_block.is_a?(Hash)
216
- merge_data_attributes(content_or_options_with_block, reflex_data_attributes(reflex))
217
- else
218
- merge_data_attributes(options, reflex_data_attributes(reflex))
219
- end
220
- content_tag(name, content_or_options_with_block, options, escape, &block)
221
- end
222
-
223
- def collection_key
224
- nil
225
- end
226
-
227
- def permit_parameter?(initial_param, new_param)
228
- initial_param != new_param
229
- end
230
-
231
- def omitted_from_state
232
- []
233
- end
234
-
235
- def after_state_initialized(parameters_changed)
236
- # called after state component has been hydrated
237
- end
238
-
239
- # def receive_params(old_state, params)
240
- # # no op
241
- # end
242
-
243
- def safe_instance_variables
244
- instance_variables.reject { |ivar| ivar.start_with?("@__vc") } - unsafe_instance_variables - omitted_from_state
245
- end
246
-
247
- private
248
-
249
- def unsafe_instance_variables
250
- [
251
- :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
252
- :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
253
- :@helpers, :@controller, :@request, :@tag_builder, :@state_initialized,
254
- :@_content_evaluated, :@_render_in_block, :@__cached_content,
255
- :@original_view_context, :@compiler,
256
- ]
257
- end
258
-
259
- def content
260
- if cached_content && !@_render_in_block
261
- cached_content
262
- else
263
- super
264
- end
265
- end
266
-
267
- def cached_content
268
- @__cached_content__
269
- end
270
-
271
- def create_safe_state
272
- new_state = {}
273
-
274
- # this will almost certainly break
275
- safe_instance_variables.each do |k|
276
- new_state[k] = instance_variable_get(k)
277
- end
278
-
279
- new_state[:@__cached_content__] = content
280
-
281
- new_state
282
- end
283
-
284
- def merge_data_attributes(options, attributes)
285
- data = options[:data]
286
- if data.nil?
287
- options[:data] = attributes
288
- else
289
- options[:data].merge! attributes
290
- end
291
- end
292
- end
293
- 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
+ attr_reader :_state_adapter
8
+
9
+ def init_stimulus_reflex
10
+ factory = ViewComponentReflex::ReflexFactory.new(self)
11
+ @stimulus_reflex ||= factory.reflex
12
+
13
+ # Always wire up new callbacks in development
14
+ if Rails.env.development?
15
+ reset_callbacks
16
+ wire_up_callbacks
17
+ elsif factory.new? # only wire up callbacks in production if they haven't been wired up yet
18
+ wire_up_callbacks
19
+ end
20
+ end
21
+
22
+ def queue_callback(key, args, blk)
23
+ callbacks(key).push({
24
+ args: args,
25
+ blk: blk
26
+ })
27
+ end
28
+
29
+ def callbacks(key)
30
+ @@callbacks ||= {}
31
+ @@callbacks[key] ||= []
32
+ end
33
+
34
+ def register_callbacks(key)
35
+ callbacks(key).each do |cb|
36
+ @stimulus_reflex.send("#{key}_reflex", *cb[:args], &cb[:blk])
37
+ end
38
+ end
39
+
40
+ def before_reflex(*args, &blk)
41
+ queue_callback(:before, args, blk)
42
+ end
43
+
44
+ def after_reflex(*args, &blk)
45
+ queue_callback(:after, args, blk)
46
+ end
47
+
48
+ def around_reflex(*args, &blk)
49
+ queue_callback(:around, args, blk)
50
+ end
51
+
52
+ def reset_callbacks
53
+ # SR uses :process as the underlying callback key
54
+ @stimulus_reflex.reset_callbacks(:process)
55
+ end
56
+
57
+ def wire_up_callbacks
58
+ register_callbacks(:before)
59
+ register_callbacks(:after)
60
+ register_callbacks(:around)
61
+ end
62
+
63
+ def state_adapter(what)
64
+ if what.is_a?(Symbol) || what.is_a?(String)
65
+ class_name = what.to_s.camelize
66
+ @_state_adapter = StateAdapter.const_get class_name
67
+ else
68
+ @_state_adapter = what
69
+ end
70
+ end
71
+ end
72
+
73
+ def self.stimulus_controller
74
+ name.chomp("Component").underscore.dasherize.gsub("/", "--")
75
+ end
76
+
77
+ def stimulus_reflex?
78
+ helpers.controller.instance_variable_get(:@stimulus_reflex)
79
+ end
80
+
81
+ def before_render
82
+ adapter.extend_component(self)
83
+ end
84
+
85
+ def component_controller(opts_or_tag = :div, opts = {}, &blk)
86
+ initialize_component
87
+
88
+ tag = :div
89
+ options = if opts_or_tag.is_a? Hash
90
+ opts_or_tag
91
+ else
92
+ tag = opts_or_tag
93
+ opts
94
+ end
95
+
96
+ options[:data] = {
97
+ controller: self.class.stimulus_controller,
98
+ key: key,
99
+ **(options[:data] || {})
100
+ }
101
+ content_tag tag, capture(&blk), options
102
+ end
103
+
104
+ def can_render_to_string?
105
+ omitted_from_state.empty?
106
+ end
107
+
108
+ # We can't truly initialize the component without the view_context,
109
+ # which isn't available in the `initialize` method. We require the
110
+ # developer to wrap components in `component_controller`, so this is where
111
+ # we truly initialize the component.
112
+ # This method is overridden in reflex.rb when the component is re-rendered. The
113
+ # override simply sets @key to element.dataset[:key]
114
+ # We don't want it to initialize the state again, and since we're rendering the component
115
+ # outside of the view, we need to skip the initialize_key method as well
116
+ def initialize_component
117
+ initialize_key
118
+ initialize_state
119
+ end
120
+
121
+ # Note to self:
122
+ # This has to be in the Component class because there are situations
123
+ # where the controller is the one rendering the component
124
+ # so we can't rely on the component created by the reflex
125
+ def initialize_state
126
+ return if state_initialized?
127
+
128
+ adapter.extend_component(self)
129
+
130
+ # newly mounted
131
+ if !stimulus_reflex? || state(@key).empty?
132
+
133
+ new_state = create_safe_state
134
+
135
+ wrap_write_async do
136
+ store_state(@key, new_state)
137
+ store_state("#{@key}_initial", new_state)
138
+ end
139
+
140
+ # updating a mounted component
141
+ else
142
+ initial_state = state("#{@key}_initial")
143
+
144
+ parameters_changed = []
145
+ state(@key).each do |k, v|
146
+ instance_value = instance_variable_get(k)
147
+ if permit_parameter?(initial_state[k], instance_value)
148
+ parameters_changed << k
149
+ wrap_write_async do
150
+ set_state("#{@key}_initial", { k => instance_value })
151
+ set_state(@key, { k => instance_value })
152
+ end
153
+ else
154
+ instance_variable_set(k, v)
155
+ end
156
+ end
157
+ after_state_initialized(parameters_changed)
158
+ end
159
+ @state_initialized = true
160
+ end
161
+
162
+ def adapter
163
+ self.class._state_adapter || ViewComponentReflex::Engine.state_adapter
164
+ end
165
+
166
+ def wrap_write_async(&blk)
167
+ adapter.wrap_write_async(&blk)
168
+ end
169
+
170
+ def set_state(key, new_state)
171
+ adapter.set_state(request, controller, key, new_state)
172
+ end
173
+
174
+ def state(key)
175
+ adapter.state(request, key)
176
+ end
177
+
178
+ def store_state(key, new_state = {})
179
+ adapter.store_state(request, key, new_state)
180
+ end
181
+
182
+ def state_initialized?
183
+ @state_initialized
184
+ end
185
+
186
+ def initialize_key
187
+ # we want the erb file that renders the component. `caller` gives the file name,
188
+ # and line number, which should be unique. We hash it to make it a nice number
189
+ erb_file = caller.select { |p| p.match? /.\.html\.(haml|erb|slim)/ }[1]
190
+ key = if erb_file
191
+ erb_file.split(":in")[0]
192
+
193
+ else
194
+ ""
195
+ end
196
+ key += collection_key.to_s if collection_key
197
+ @key = Digest::SHA2.hexdigest(key)
198
+ end
199
+
200
+ # Helper to use to create the proper reflex data attributes for an element
201
+ def reflex_data_attributes(reflex)
202
+ action, method = reflex.to_s.split("->")
203
+ if method.nil?
204
+ method = action
205
+ action = "click"
206
+ end
207
+
208
+ {
209
+ reflex: "#{action}->#{self.class.name}##{method}",
210
+ key: key
211
+ }
212
+ end
213
+
214
+ def reflex_tag(reflex, name, content_or_options_with_block = {}, options = {}, escape = true, &block)
215
+ if content_or_options_with_block.is_a?(Hash)
216
+ merge_data_attributes(content_or_options_with_block, reflex_data_attributes(reflex))
217
+ else
218
+ merge_data_attributes(options, reflex_data_attributes(reflex))
219
+ end
220
+ content_tag(name, content_or_options_with_block, options, escape, &block)
221
+ end
222
+
223
+ def collection_key
224
+ nil
225
+ end
226
+
227
+ def permit_parameter?(initial_param, new_param)
228
+ initial_param != new_param
229
+ end
230
+
231
+ def omitted_from_state
232
+ []
233
+ end
234
+
235
+ def after_state_initialized(parameters_changed)
236
+ # called after state component has been hydrated
237
+ end
238
+
239
+ # def receive_params(old_state, params)
240
+ # # no op
241
+ # end
242
+
243
+ def safe_instance_variables
244
+ instance_variables.reject { |ivar| ivar.start_with?("@__vc") } - unsafe_instance_variables - omitted_from_state
245
+ end
246
+
247
+ private
248
+
249
+ def unsafe_instance_variables
250
+ [
251
+ :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
252
+ :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
253
+ :@helpers, :@controller, :@request, :@tag_builder, :@state_initialized,
254
+ :@_content_evaluated, :@_render_in_block, :@__cached_content,
255
+ :@original_view_context, :@compiler,
256
+ ]
257
+ end
258
+
259
+ def content
260
+ if cached_content && !@_render_in_block
261
+ cached_content
262
+ else
263
+ super
264
+ end
265
+ end
266
+
267
+ def cached_content
268
+ @__cached_content__
269
+ end
270
+
271
+ def create_safe_state
272
+ new_state = {}
273
+
274
+ # this will almost certainly break
275
+ safe_instance_variables.each do |k|
276
+ new_state[k] = instance_variable_get(k)
277
+ end
278
+
279
+ new_state[:@__cached_content__] = content
280
+
281
+ new_state
282
+ end
283
+
284
+ def merge_data_attributes(options, attributes)
285
+ data = options[:data]
286
+ if data.nil?
287
+ options[:data] = attributes
288
+ else
289
+ options[:data].merge! attributes
290
+ end
291
+ end
292
+ end
293
+ end