view_component_reflex 3.1.2 → 3.1.7

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,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