view_component_reflex 2.5.0 → 3.0.1

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,214 +1,211 @@
1
- module ViewComponentReflex
2
- class Component < ViewComponent::Base
3
- class << self
4
- def init_stimulus_reflex
5
- factory = ViewComponentReflex::ReflexFactory.new(self)
6
- @stimulus_reflex ||= factory.reflex
7
- wire_up_callbacks if factory.new?
8
- end
9
-
10
- def reflex_base_class(new_base_class = nil)
11
- if new_base_class.nil?
12
- @reflex_base_class ||= ViewComponentReflex::Reflex
13
- else
14
- if new_base_class <= ViewComponentReflex::Reflex
15
- @reflex_base_class = new_base_class
16
- else
17
- raise StandardError.new("The reflex base class must inherit from ViewComponentReflex::Reflex")
18
- end
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 wire_up_callbacks
53
- register_callbacks(:before)
54
- register_callbacks(:after)
55
- register_callbacks(:around)
56
- end
57
- end
58
-
59
- def self.stimulus_controller
60
- name.chomp("Component").underscore.dasherize
61
- end
62
-
63
- def stimulus_reflex?
64
- helpers.controller.instance_variable_get(:@stimulus_reflex)
65
- end
66
-
67
- def component_controller(opts_or_tag = :div, opts = {}, &blk)
68
- init_key
69
-
70
- tag = :div
71
- options = if opts_or_tag.is_a? Hash
72
- opts_or_tag
73
- else
74
- tag = opts_or_tag
75
- opts
76
- end
77
-
78
- options[:data] = {
79
- controller: self.class.stimulus_controller,
80
- key: key,
81
- **(options[:data] || {})
82
- }
83
- content_tag tag, capture(&blk), options
84
- end
85
-
86
- def can_render_to_string?
87
- omitted_from_state.empty?
88
- end
89
-
90
- # key is required if you're using state
91
- # We can't initialize the session state in the initial method
92
- # because it doesn't have a view_context yet
93
- # This is the next best place to do it
94
- def init_key
95
- # we want the erb file that renders the component. `caller` gives the file name,
96
- # and line number, which should be unique. We hash it to make it a nice number
97
- erb_file = caller.select { |p| p.match? /.\.html\.(haml|erb|slim)/ }[1]
98
- key = if erb_file
99
- Digest::SHA2.hexdigest(erb_file.split(":in")[0])
100
- else
101
- ""
102
- end
103
- key += collection_key.to_s if collection_key
104
- @key = key
105
- end
106
-
107
- # Helper to use to create the proper reflex data attributes for an element
108
- def reflex_data_attributes(reflex)
109
- action, method = reflex.to_s.split("->")
110
- if method.nil?
111
- method = action
112
- action = "click"
113
- end
114
-
115
- {
116
- reflex: "#{action}->#{self.class.name}##{method}",
117
- key: key
118
- }
119
- end
120
-
121
- def reflex_tag(reflex, name, content_or_options_with_block = {}, options = {}, escape = true, &block)
122
- if content_or_options_with_block.is_a?(Hash)
123
- merge_data_attributes(content_or_options_with_block, reflex_data_attributes(reflex))
124
- else
125
- merge_data_attributes(options, reflex_data_attributes(reflex))
126
- end
127
- content_tag(name, content_or_options_with_block, options, escape, &block)
128
- end
129
-
130
- def collection_key
131
- nil
132
- end
133
-
134
- def permit_parameter?(initial_param, new_param)
135
- initial_param != new_param
136
- end
137
-
138
- def omitted_from_state
139
- []
140
- end
141
-
142
- def after_state_initialized(parameters_changed)
143
- # called after state component has been hydrated
144
- end
145
-
146
- # def receive_params(old_state, params)
147
- # # no op
148
- # end
149
-
150
- def key
151
- # initialize session state
152
- if !stimulus_reflex? || ViewComponentReflex::Engine.state_adapter.state(request, @key).empty?
153
-
154
- new_state = create_safe_state
155
-
156
- ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
157
- ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", new_state)
158
- elsif !@initialized_state
159
- initial_state = ViewComponentReflex::Engine.state_adapter.state(request, "#{@key}_initial")
160
-
161
- # incoming_params = safe_instance_variables.each_with_object({}) { |var, obj| obj[var] = instance_variable_get(var) }
162
- # receive_params(ViewComponentReflex::Engine.state_adapter.state(request, @key), incoming_params)
163
-
164
- parameters_changed = []
165
- ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
166
- instance_value = instance_variable_get(k)
167
- if permit_parameter?(initial_state[k], instance_value)
168
- parameters_changed << k
169
- ViewComponentReflex::Engine.state_adapter.set_state(request, controller, "#{@key}_initial", {k => instance_value})
170
- ViewComponentReflex::Engine.state_adapter.set_state(request, controller, @key, {k => instance_value})
171
- else
172
- instance_variable_set(k, v)
173
- end
174
- end
175
- after_state_initialized(parameters_changed)
176
- @initialized_state = true
177
- end
178
- @key
179
- end
180
-
181
- def safe_instance_variables
182
- instance_variables - unsafe_instance_variables
183
- end
184
-
185
- private
186
-
187
- def unsafe_instance_variables
188
- [
189
- :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
190
- :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
191
- :@helpers, :@controller, :@request, :@tag_builder, :@initialized_state
192
- ]
193
- end
194
-
195
- def create_safe_state
196
- new_state = {}
197
-
198
- # this will almost certainly break
199
- safe_instance_variables.each do |k|
200
- new_state[k] = instance_variable_get(k) unless omitted_from_state.include?(k)
201
- end
202
- new_state
203
- end
204
-
205
- def merge_data_attributes(options, attributes)
206
- data = options[:data]
207
- if data.nil?
208
- options[:data] = attributes
209
- else
210
- options[:data].merge! attributes
211
- end
212
- end
213
- end
214
- end
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
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 = :div
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,10 +1,11 @@
1
- require "stimulus_reflex"
2
- require 'view_component_reflex/reflex_factory'
3
- require "view_component_reflex/state_adapter/session"
4
- require "view_component_reflex/state_adapter/memory"
5
- require "view_component_reflex/reflex"
6
- require "view_component_reflex/engine"
7
-
8
- module ViewComponentReflex
9
- # Your code goes here...
10
- end
1
+ require "stimulus_reflex"
2
+ require 'view_component_reflex/reflex_factory'
3
+ require "view_component_reflex/state_adapter/session"
4
+ require "view_component_reflex/state_adapter/memory"
5
+ require "view_component_reflex/state_adapter/redis"
6
+ require "view_component_reflex/reflex"
7
+ require "view_component_reflex/engine"
8
+
9
+ module ViewComponentReflex
10
+ # Your code goes here...
11
+ end
@@ -1,36 +1,37 @@
1
- module ViewComponentReflex
2
- class Engine < ::Rails::Engine
3
- class << self
4
- mattr_accessor :state_adapter
5
-
6
- self.state_adapter = StateAdapter::Session
7
- end
8
-
9
- config.to_prepare do
10
- StimulusReflex::Channel.class_eval do
11
- unless instance_methods.include?(:receive_original)
12
- alias_method :receive_original, :receive
13
- def receive(data)
14
- target = data["target"].to_s
15
- reflex_name, _ = target.split("#")
16
- reflex_name = reflex_name.camelize
17
- component_name = reflex_name.end_with?("Reflex") ? reflex_name[0...-6] : reflex_name
18
- if component_name.end_with?("Component")
19
- begin
20
- component_name.constantize.init_stimulus_reflex
21
- rescue
22
- p "Tried to initialize view_component_reflex on #{component_name}, but it's not a view_component_reflex"
23
- end
24
-
25
- end
26
- receive_original(data)
27
- end
28
- end
29
- end
30
- end
31
-
32
- def self.configure
33
- yield self if block_given?
34
- end
35
- end
36
- end
1
+ module ViewComponentReflex
2
+ class Engine < ::Rails::Engine
3
+
4
+ mattr_accessor :state_adapter
5
+ Engine.state_adapter = StateAdapter::Session
6
+
7
+ config.to_prepare do
8
+ StimulusReflex::Channel.class_eval do
9
+ unless instance_methods.include?(:receive_original)
10
+ alias_method :receive_original, :receive
11
+ def receive(data)
12
+ target = data["target"].to_s
13
+ reflex_name, _ = target.split("#")
14
+ reflex_name = reflex_name.camelize
15
+ component_name = reflex_name.end_with?("Reflex") ? reflex_name[0...-6] : reflex_name
16
+ component = begin
17
+ component_name.constantize
18
+ rescue
19
+ # Since every reflex runs through this monkey patch, we're just going to ignore the ones that aren't for components
20
+ end
21
+
22
+ if component&.respond_to?(:init_stimulus_reflex)
23
+ component.init_stimulus_reflex
24
+ else
25
+ p "Tried to initialize view_component_reflex on #{component_name}, but it's not a view_component_reflex"
26
+ end
27
+ receive_original(data)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def self.configure
34
+ yield self if block_given?
35
+ end
36
+ end
37
+ end