view_component_reflex 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52a985ff5f053dfb2efd1d2d14fd95e288df76bddc2a217cc8f77a2c03d411d8
4
- data.tar.gz: 3708aadf74378afd1f1e86f5fdbf7628e246a5f573714684754d08c77685b987
3
+ metadata.gz: 48be01529611392c673d2ca154ba25657bde6564cfb9238c78debea251f3aa11
4
+ data.tar.gz: 41d40953951fbd6d26fa4044ad41e1ca6b5b2a969439404d78f755853ea1acca
5
5
  SHA512:
6
- metadata.gz: 42ea4a59d6705a4a34fa90a274e31785ab6cdc70184239063921de8050168021e54b43fac3a1504ce0f749171b776800cd0eaaffbb1d0f629e71e9c1397f7508
7
- data.tar.gz: 1699da2877f8eddc8abb9f7cf8b48feab0283aaad2f031090f4c04677e2cbc0997eef6521c244d50b0abaa3f0746baac9d10d86ecc60021c91c7bd191342aa00
6
+ metadata.gz: a6b645acacb1f3716b05cf134225255096aa80ef47a131949d79f0cbfec69a9623c6f26d061d381f6637ff26501eeaeabdfe17c52e7cb2be027eda0e5eba91ec
7
+ data.tar.gz: 9d714a2889b6d4f100cd816f6068699e86966807bf529353b9ab2699738d649a5169116926e75fd5690a2cbb7a0583574cf164b52518b4cff29e86745a38d803
data/README.md CHANGED
@@ -1,345 +1,361 @@
1
- # ViewComponentReflex
2
-
3
- ViewComponentReflex allows you to write reflexes right in your view component code.
4
-
5
- It builds upon [stimulus_reflex](https://github.com/hopsoft/stimulus_reflex) and [view_component](https://github.com/github/view_component)
6
-
7
- ## Usage
8
-
9
- You can add reflexes to your component by adding inheriting from `ViewComponentReflex::Component`.
10
-
11
- This will act as if you created a reflex with the method `my_cool_stuff`. To call this reflex, add `data-reflex="click->MyComponentReflex#my_cool_reflex"`, just like you're
12
- using stimulus reflex.
13
-
14
- ViewComponentReflex will maintain your component's instance variables between renders. You need to include `data-key=<%= key %>` on your root element, as well
15
- as any element that stimulates a reflex. ViewComponent is inherently state-less, so the key is used to reconcile state to its respective component.
16
-
17
- ### Example
18
- ```ruby
19
- # counter_component.rb
20
- class CounterComponent < ViewComponentReflex::Component
21
- def initialize
22
- @count = 0
23
- end
24
-
25
- def increment
26
- @count += 1
27
- end
28
- end
29
- ```
30
-
31
- ```erb
32
- # counter_component.html.erb
33
- <%= component_controller do %>
34
- <p><%= @count %></p>
35
- <%= reflex_tag :increment, :button, "Click" %>
36
- <% end %>
37
- ```
38
-
39
- ## Collections
40
-
41
- In order to reconcile state to components in collections, you can specify a `collection_key` method that returns some
42
- value unique to that component.
43
-
44
- ```ruby
45
- class TodoComponent < ViewComponentReflex::Component
46
- def initialize(todo:)
47
- @todo = todo
48
- end
49
-
50
- def collection_key
51
- @todo.id
52
- end
53
- end
54
- #
55
- <%= render(TodoComponent.with_collection(Todo.all)) %>
56
- ```
57
-
58
- ## API
59
-
60
- ### permit_parameter?(initial_param, new_params)
61
- If a new parameter is passed to the component during rendering, it is used instead of what's in state.
62
- If you're storing instances in state, you can use this to properly compare them.
63
-
64
- ```ruby
65
- def permit_parameter?(initial_param, new_param)
66
- if new_param.instance_of? MyModel
67
- new_param.id == @my_model.id
68
- else
69
- super
70
- end
71
- end
72
- ```
73
-
74
- ### omitted_from_state
75
- Return an array of instance variables you want to omit from state. Only really useful if you're using the session state
76
- adapter, and you have an instance variable that can't be serialized.
77
-
78
- ```ruby
79
- def omitted_from_state
80
- [:@form]
81
- end
82
- ```
83
-
84
- ### reflex_tag(reflex, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
85
- This shares the same definition as `content_tag`, except it accepts a reflex as the first parameter.
86
-
87
- ```erb
88
- <%= reflex_tag :increment, :button, "Click me!" %>
89
- ```
90
-
91
- Would add a click handler to the `increment` method on your component.
92
-
93
- To use a non-click event, specific that with `->` notation
94
-
95
- ```erb
96
- <%= reflex_tag "mouseenter->increment", :button, "Click me!" %>
97
- ```
98
-
99
- ### reflex_data_attributes(reflex)
100
-
101
- This helper will give you the data attributes used in the reflex_tag above if you want to build your own elements.
102
-
103
- Build your own tag:
104
-
105
- ```erb
106
- <%= link_to (image_tag photo.image.url(:medium)), data: reflex_data_attributes(:increment) %>
107
- ```
108
-
109
- Render a ViewComponent
110
-
111
- ```erb
112
- <%= render ButtonComponent.new(data: reflex_data_attributes("mouseenter->increment")) %>
113
- ```
114
-
115
- Make sure that you assign the reflex_data_attributes to the correct element in your component.
116
-
117
- ### collection_key
118
- If you're rendering a component as a collection with `MyComponent.with_collection(SomeCollection)`, you must define this method to return some unique value for the component.
119
- This is used to reconcile state in the background.
120
-
121
- ```ruby
122
- def initialize
123
- @my_model = MyModel.new
124
- end
125
-
126
- def collection_key
127
- @my_model.id
128
- end
129
- ```
130
-
131
- ### stimulate(target, data)
132
- Stimulate another reflex from within your component. This typically requires the key of the component you're stimulating
133
- which can be passed in via parameters.
134
-
135
- ```ruby
136
- def initialize(parent_key)
137
- @parent_key = parent_key
138
- end
139
-
140
- def stimulate_other
141
- stimulate("OtherComponent#method", { key: @parent_key })
142
- end
143
- ```
144
-
145
- ### refresh!(selectors)
146
- Refresh a specific element on the page. Using this will implicitly run `prevent_render!`.
147
- If you want to render a specific element, as well as the component, a common pattern would be to pass `selector` as one of the parameters
148
-
149
- ```
150
- def my_method
151
- refresh! '#my-special-element', selector
152
- end
153
- ```
154
-
155
- ### selector
156
- Returns the unique selector for this component. Useful to pass to `refresh!` when refreshing custom elements.
157
-
158
- ### prevent_refresh!
159
- By default, VCR will re-render your component after it executes your method. `prevent_refresh!` prevents this from happening.
160
-
161
- ```ruby
162
- def my_method
163
- prevent_refresh!
164
- @foo = :bar
165
- end # the rendered page will not reflect this change
166
- ```
167
-
168
- ### refresh_all!
169
- Refresh the entire body of the page
170
-
171
- ```ruby
172
- def do_some_global_action
173
- prevent_refresh!
174
- session[:model] = MyModel.new
175
- refresh_all!
176
- end
177
- ```
178
-
179
- ### key
180
- This is a key unique to a particular component. It's used to reconcile state between renders, and should be passed as a data attribute whenever a reflex is called
181
-
182
- ```erb
183
- <button type="button" data-reflex="click->MyComponent#do_something" data-key="<%= key %>">Click me!</button>
184
- ```
185
-
186
- ### component_controller(options = {}, &blk)
187
- This is a view helper to properly connect VCR to the component. It outputs `<div data-controller="my-controller" key=<%= key %></div>`
188
- You *must* wrap your component in this for everything to work properly.
189
-
190
- ```erb
191
- <%= component_controller do %>
192
- <p><%= @count %></p
193
- <% end %>
194
- ```
195
-
196
- ### after_state_initialized(parameters_changed)
197
-
198
- This is called after the state has been inserted in the component. You can use this to run conditional functions after
199
- some parameter has superseeded whatever's in state
200
-
201
- ```
202
- def after_state_initialized(parameters_changed)
203
- if parameters_changed.include?(:@filter)
204
- calculate_visible_rows
205
- end
206
- end
207
- ```
208
-
209
- ## Custom reflex base class
210
- Reflexes typically inherit from a base ApplicationReflex. You can define the base class for a view_component_reflex by using the `reflex_base_class` method.
211
- The parent class must inherit ViewComponentReflex::Reflex, and will throw an error if it does not.
212
-
213
- ```ruby
214
- class ApplicationReflex < ViewComponentReflex::Reflex
215
-
216
- end
217
-
218
-
219
- class MyComponent < ViewComponentReflex::Component
220
- reflex_base_class ApplicationReflex
221
- end
222
- ```
223
-
224
- ## Common patterns
225
- A lot of the time, you only need to update specific components when changing instance variables. For example, changing `@loading` might only need
226
- to display a spinner somewhere on the page. You can define setters to implicitly render the appropriate pieces of dom whenever that variable is set
227
-
228
- ```ruby
229
- def initialize
230
- @loading = false
231
- end
232
-
233
- def loading=(new_value)
234
- @loading = new_value
235
- refresh! '#loader'
236
- end
237
-
238
- def do_expensive_action
239
- prevent_refresh!
240
-
241
- self.loading = true
242
- execute_it
243
- self.loading = false
244
- end
245
- ```
246
-
247
- ```erb
248
- <%= component_controller do %>
249
- <div id="loader">
250
- <% if @loading %>
251
- <p>Loading...</p>
252
- <% end %>
253
- </div>
254
-
255
- <button type="button" data-reflex="click->MyComponent#do_expensive_action" data-key="<%= key %>">Click me!</button>
256
- <% end
257
- ```
258
-
259
- ## State
260
-
261
- By default (since version `2.3.2`), view_component_reflex stores component state in session. You can optionally set the state adapter
262
- to use the memory by changing `config.state_adapter` to `ViewComponentReflex::StateAdapter::Memory`.
263
-
264
- ## Custom State Adapters
265
-
266
- ViewComponentReflex uses session for its state by default. To change this, add
267
- an initializer to `config/initializers/view_component_reflex.rb`.
268
-
269
- ```ruby
270
- ViewComponentReflex::Engine.configure do |config|
271
- config.state_adapter = YourAdapter
272
- end
273
- ```
274
-
275
- `YourAdapter` should implement
276
-
277
- ```ruby
278
- class YourAdapter
279
- ##
280
- # request - a rails request object
281
- # key - a unique string that identifies the component instance
282
- def self.state(request, key)
283
- # Return state for a given key
284
- end
285
-
286
- ##
287
- # set_state is used to modify the state.
288
- #
289
- # request - a rails request object
290
- # controller - the current controller
291
- # key - a unique string that identifies the component
292
- # new_state - the new state to set
293
- def self.set_state(request, controller, key, new_state)
294
- # update the state
295
- end
296
-
297
-
298
- ##
299
- # store_state is used to replace the state entirely. It only accepts
300
- # a request object, rather than a reflex because it's called from the component's
301
- # side with the component's instance variables.
302
- #
303
- # request - a rails request object
304
- # key - a unique string that identifies the component instance
305
- # new_state - a hash containing the component state
306
- def self.store_state(request, key, new_state = {})
307
- # replace the state
308
- end
309
- end
310
- ```
311
-
312
-
313
- ## Installation
314
- Add this line to your application's Gemfile:
315
-
316
- ```ruby
317
- gem 'view_component_reflex'
318
- ```
319
-
320
- And then execute:
321
- ```bash
322
- $ bundle
323
- ```
324
-
325
- Or install it yourself as:
326
- ```bash
327
- $ gem install view_component_reflex
328
- ```
329
-
330
- # Common problems
331
-
332
- ## Uninitialized constants \<component\>Reflex
333
- A component needs to be wrapped in `<%= component_controller do %>` in order to properly initialize, otherwise the Reflex class won't get created.
334
-
335
- ## License
336
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
337
-
338
- ## Caveats
339
-
340
- State uses session to maintain state as of right now. It also assumes your component view is written with a file extension of either `.html.erb`, `.html.haml` or `.html.slim`.
341
-
342
- ## Support me
343
-
344
- <a href="https://www.buymeacoffee.com/jleblanc" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="40" ></a>
345
-
1
+ # ViewComponentReflex
2
+
3
+ ViewComponentReflex allows you to write reflexes right in your view component code.
4
+
5
+ It builds upon [stimulus_reflex](https://github.com/hopsoft/stimulus_reflex) and [view_component](https://github.com/github/view_component)
6
+
7
+ ## Usage
8
+
9
+ You can add reflexes to your component by adding inheriting from `ViewComponentReflex::Component`.
10
+
11
+ This will act as if you created a reflex with the method `my_cool_stuff`. To call this reflex, add `data-reflex="click->MyComponentReflex#my_cool_reflex"`, just like you're
12
+ using stimulus reflex.
13
+
14
+ ViewComponentReflex will maintain your component's instance variables between renders. You need to include `data-key=<%= key %>` on your root element, as well
15
+ as any element that stimulates a reflex. ViewComponent is inherently state-less, so the key is used to reconcile state to its respective component.
16
+
17
+ ### Example
18
+ ```ruby
19
+ # counter_component.rb
20
+ class CounterComponent < ViewComponentReflex::Component
21
+ def initialize
22
+ @count = 0
23
+ end
24
+
25
+ def increment
26
+ @count += 1
27
+ end
28
+ end
29
+ ```
30
+
31
+ ```erb
32
+ # counter_component.html.erb
33
+ <%= component_controller do %>
34
+ <p><%= @count %></p>
35
+ <%= reflex_tag :increment, :button, "Click" %>
36
+ <% end %>
37
+ ```
38
+
39
+ ## Collections
40
+
41
+ In order to reconcile state to components in collections, you can specify a `collection_key` method that returns some
42
+ value unique to that component.
43
+
44
+ ```ruby
45
+ class TodoComponent < ViewComponentReflex::Component
46
+ def initialize(todo:)
47
+ @todo = todo
48
+ end
49
+
50
+ def collection_key
51
+ @todo.id
52
+ end
53
+ end
54
+ #
55
+ <%= render(TodoComponent.with_collection(Todo.all)) %>
56
+ ```
57
+
58
+ ## API
59
+
60
+ ### permit_parameter?(initial_param, new_params)
61
+ If a new parameter is passed to the component during rendering, it is used instead of what's in state.
62
+ If you're storing instances in state, you can use this to properly compare them.
63
+
64
+ ```ruby
65
+ def permit_parameter?(initial_param, new_param)
66
+ if new_param.instance_of? MyModel
67
+ new_param.id == @my_model.id
68
+ else
69
+ super
70
+ end
71
+ end
72
+ ```
73
+
74
+ ### omitted_from_state
75
+ Return an array of instance variables you want to omit from state. Only really useful if you're using the session state
76
+ adapter, and you have an instance variable that can't be serialized.
77
+
78
+ ```ruby
79
+ def omitted_from_state
80
+ [:@form]
81
+ end
82
+ ```
83
+
84
+ ### reflex_tag(reflex, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
85
+ This shares the same definition as `content_tag`, except it accepts a reflex as the first parameter.
86
+
87
+ ```erb
88
+ <%= reflex_tag :increment, :button, "Click me!" %>
89
+ ```
90
+
91
+ Would add a click handler to the `increment` method on your component.
92
+
93
+ To use a non-click event, specific that with `->` notation
94
+
95
+ ```erb
96
+ <%= reflex_tag "mouseenter->increment", :button, "Click me!" %>
97
+ ```
98
+
99
+ ### reflex_data_attributes(reflex)
100
+
101
+ This helper will give you the data attributes used in the reflex_tag above if you want to build your own elements.
102
+
103
+ Build your own tag:
104
+
105
+ ```erb
106
+ <%= link_to (image_tag photo.image.url(:medium)), data: reflex_data_attributes(:increment) %>
107
+ ```
108
+
109
+ Render a ViewComponent
110
+
111
+ ```erb
112
+ <%= render ButtonComponent.new(data: reflex_data_attributes("mouseenter->increment")) %>
113
+ ```
114
+
115
+ Make sure that you assign the reflex_data_attributes to the correct element in your component.
116
+
117
+ ### collection_key
118
+ If you're rendering a component as a collection with `MyComponent.with_collection(SomeCollection)`, you must define this method to return some unique value for the component.
119
+ This is used to reconcile state in the background.
120
+
121
+ ```ruby
122
+ def initialize
123
+ @my_model = MyModel.new
124
+ end
125
+
126
+ def collection_key
127
+ @my_model.id
128
+ end
129
+ ```
130
+
131
+ ### stimulate(target, data)
132
+ Stimulate another reflex from within your component. This typically requires the key of the component you're stimulating
133
+ which can be passed in via parameters.
134
+
135
+ ```ruby
136
+ def initialize(parent_key)
137
+ @parent_key = parent_key
138
+ end
139
+
140
+ def stimulate_other
141
+ stimulate("OtherComponent#method", { key: @parent_key })
142
+ end
143
+ ```
144
+
145
+ ### refresh!(selectors)
146
+ Refresh a specific element on the page. Using this will implicitly run `prevent_render!`.
147
+ If you want to render a specific element, as well as the component, a common pattern would be to pass `selector` as one of the parameters
148
+
149
+ ```
150
+ def my_method
151
+ refresh! '#my-special-element', selector
152
+ end
153
+ ```
154
+
155
+ ### selector
156
+ Returns the unique selector for this component. Useful to pass to `refresh!` when refreshing custom elements.
157
+
158
+ ### prevent_refresh!
159
+ By default, VCR will re-render your component after it executes your method. `prevent_refresh!` prevents this from happening.
160
+
161
+ ```ruby
162
+ def my_method
163
+ prevent_refresh!
164
+ @foo = :bar
165
+ end # the rendered page will not reflect this change
166
+ ```
167
+
168
+ ### refresh_all!
169
+ Refresh the entire body of the page
170
+
171
+ ```ruby
172
+ def do_some_global_action
173
+ prevent_refresh!
174
+ session[:model] = MyModel.new
175
+ refresh_all!
176
+ end
177
+ ```
178
+
179
+ ### key
180
+ This is a key unique to a particular component. It's used to reconcile state between renders, and should be passed as a data attribute whenever a reflex is called
181
+
182
+ ```erb
183
+ <button type="button" data-reflex="click->MyComponent#do_something" data-key="<%= key %>">Click me!</button>
184
+ ```
185
+
186
+ ### component_controller(options = {}, &blk)
187
+ This is a view helper to properly connect VCR to the component. It outputs `<div data-controller="my-controller" key=<%= key %></div>`
188
+ You *must* wrap your component in this for everything to work properly.
189
+
190
+ ```erb
191
+ <%= component_controller do %>
192
+ <p><%= @count %></p
193
+ <% end %>
194
+ ```
195
+
196
+ ### after_state_initialized(parameters_changed)
197
+
198
+ This is called after the state has been inserted in the component. You can use this to run conditional functions after
199
+ some parameter has superseeded whatever's in state
200
+
201
+ ```
202
+ def after_state_initialized(parameters_changed)
203
+ if parameters_changed.include?(:@filter)
204
+ calculate_visible_rows
205
+ end
206
+ end
207
+ ```
208
+
209
+ ## Custom reflex base class
210
+ Reflexes typically inherit from a base ApplicationReflex. You can define the base class for a view_component_reflex by using the `reflex_base_class` method.
211
+ The parent class must inherit ViewComponentReflex::Reflex, and will throw an error if it does not.
212
+
213
+ ```ruby
214
+ class ApplicationReflex < ViewComponentReflex::Reflex
215
+
216
+ end
217
+
218
+
219
+ class MyComponent < ViewComponentReflex::Component
220
+ reflex_base_class ApplicationReflex
221
+ end
222
+ ```
223
+
224
+ ## Common patterns
225
+ A lot of the time, you only need to update specific components when changing instance variables. For example, changing `@loading` might only need
226
+ to display a spinner somewhere on the page. You can define setters to implicitly render the appropriate pieces of dom whenever that variable is set
227
+
228
+ ```ruby
229
+ def initialize
230
+ @loading = false
231
+ end
232
+
233
+ def loading=(new_value)
234
+ @loading = new_value
235
+ refresh! '#loader'
236
+ end
237
+
238
+ def do_expensive_action
239
+ prevent_refresh!
240
+
241
+ self.loading = true
242
+ execute_it
243
+ self.loading = false
244
+ end
245
+ ```
246
+
247
+ ```erb
248
+ <%= component_controller do %>
249
+ <div id="loader">
250
+ <% if @loading %>
251
+ <p>Loading...</p>
252
+ <% end %>
253
+ </div>
254
+
255
+ <button type="button" data-reflex="click->MyComponent#do_expensive_action" data-key="<%= key %>">Click me!</button>
256
+ <% end
257
+ ```
258
+
259
+ ## State
260
+
261
+ By default (since version `2.3.2`), view_component_reflex stores component state in session. You can optionally set the state adapter
262
+ to use the memory by changing `config.state_adapter` to `ViewComponentReflex::StateAdapter::Memory`.
263
+
264
+ ## Custom State Adapters
265
+
266
+ ViewComponentReflex uses session for its state by default. To change this, add
267
+ an initializer to `config/initializers/view_component_reflex.rb`.
268
+
269
+ ```ruby
270
+ ViewComponentReflex::Engine.configure do |config|
271
+ config.state_adapter = YourAdapter
272
+ end
273
+ ```
274
+
275
+
276
+ ## Existing Fast Redis based State Adapter
277
+
278
+ This adapter uses hmset and hgetall to reduce the number of operations.
279
+ This is the recommended adapter if you are using AnyCable.
280
+
281
+ ```ruby
282
+ ViewComponentReflex::Engine.configure do |config|
283
+ config.state_adapter = ViewComponentReflex::StateAdapter::Redis.new(
284
+ redis_opts: {
285
+ url: "redis://localhost:6379/1", driver: :hiredis
286
+ },
287
+ ttl: 3600)
288
+ end
289
+ ```
290
+
291
+ `YourAdapter` should implement
292
+
293
+ ```ruby
294
+ class YourAdapter
295
+ ##
296
+ # request - a rails request object
297
+ # key - a unique string that identifies the component instance
298
+ def self.state(request, key)
299
+ # Return state for a given key
300
+ end
301
+
302
+ ##
303
+ # set_state is used to modify the state.
304
+ #
305
+ # request - a rails request object
306
+ # controller - the current controller
307
+ # key - a unique string that identifies the component
308
+ # new_state - the new state to set
309
+ def self.set_state(request, controller, key, new_state)
310
+ # update the state
311
+ end
312
+
313
+
314
+ ##
315
+ # store_state is used to replace the state entirely. It only accepts
316
+ # a request object, rather than a reflex because it's called from the component's
317
+ # side with the component's instance variables.
318
+ #
319
+ # request - a rails request object
320
+ # key - a unique string that identifies the component instance
321
+ # new_state - a hash containing the component state
322
+ def self.store_state(request, key, new_state = {})
323
+ # replace the state
324
+ end
325
+ end
326
+ ```
327
+
328
+
329
+ ## Installation
330
+ Add this line to your application's Gemfile:
331
+
332
+ ```ruby
333
+ gem 'view_component_reflex'
334
+ ```
335
+
336
+ And then execute:
337
+ ```bash
338
+ $ bundle
339
+ ```
340
+
341
+ Or install it yourself as:
342
+ ```bash
343
+ $ gem install view_component_reflex
344
+ ```
345
+
346
+ # Common problems
347
+
348
+ ## Uninitialized constants \<component\>Reflex
349
+ A component needs to be wrapped in `<%= component_controller do %>` in order to properly initialize, otherwise the Reflex class won't get created.
350
+
351
+ ## License
352
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
353
+
354
+ ## Caveats
355
+
356
+ State uses session to maintain state as of right now. It also assumes your component view is written with a file extension of either `.html.erb`, `.html.haml` or `.html.slim`.
357
+
358
+ ## Support me
359
+
360
+ <a href="https://www.buymeacoffee.com/jleblanc" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="40" ></a>
361
+