view_component_reflex 1.1.1 → 1.6.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 163bc65c20bb1ac85bb9e15ee2ce4fbf7039a9d90cbe7c4cb1591d03699edc71
4
- data.tar.gz: fb80faac53511b705e0170da3cfae4c7c82c1a59c6bf773e8b86eea30463b9f5
3
+ metadata.gz: 0d42e80a31d1a4a27c828f02fc584e9c5eb6660a6d5697e0db3a9631be358dec
4
+ data.tar.gz: 96450604f1d0965c104171a8b1d667f6d4dc38019e0c6434fd974ff80a3d9f26
5
5
  SHA512:
6
- metadata.gz: 9936a4885f9b8e625eca11ac78af316f475764688a5fd2aca9f098aabd802c5e578bdca12266b7176c52c3ff6302dcf7c703e1dd902194bc2dd8c20a1071ab14
7
- data.tar.gz: e5fca59e3e33091ee7b53df0d0cf40007b476835f141cd3f717d7b86e14fde3cec8ffce960a4f23b7ffe5f12a48d10a6917843a9e33c67d911b76485187796c7
6
+ metadata.gz: deaf8859ae5816796b965ba5c7e1022032253f44fc1e6c94bcdda8dd481d20afb1319a63c99acc98e184908c5c0b299384e1b77cecef4cd5099263bc4f0d4606
7
+ data.tar.gz: 6da8e3668c5cf6f2dd64c770eada3b381669dd2dac2cf11dc65b2f9e7a94d4e7d450a563506459057642d0f97629d582d1935a9eef7bbf61acfc084402b2e1cd
data/README.md CHANGED
@@ -30,7 +30,7 @@ end
30
30
  # counter_component.html.erb
31
31
  <%= component_controller do %>
32
32
  <p><%= @count %></p>
33
- <button type="button" data-reflex="click->CounterComponentReflex#increment" data-key="<%= key %>">Click</button>
33
+ <%= reflex_tag :increment, :button, "Click" %>
34
34
  <% end %>
35
35
  ```
36
36
 
@@ -39,7 +39,7 @@ end
39
39
  In order to reconcile state to components in collections, you can specify a `collection_key` method that returns some
40
40
  value unique to that component.
41
41
 
42
- ```
42
+ ```ruby
43
43
  class TodoComponent < ViewComponentReflex::Component
44
44
  def initialize(todo:)
45
45
  @todo = todo
@@ -53,6 +53,134 @@ end
53
53
  <%= render(TodoComponent.with_collection(Todo.all)) %>
54
54
  ```
55
55
 
56
+ ## API
57
+
58
+ ### permit_parameter?(initial_param, new_params)
59
+ If a new parameter is passed to the component during rendering, it is used instead of what's in state.
60
+ If you're storing instances in state, you can use this to properly compare them.
61
+
62
+ ```ruby
63
+ def permit_parameter?(initial_param, new_param)
64
+ if new_param.instance_of? MyModel
65
+ new_param.id == @my_model.id
66
+ else
67
+ super
68
+ end
69
+ end
70
+ ```
71
+
72
+ ### omitted_from_state
73
+ Return an array of instance variables you want to omit from state. Useful if you have an object
74
+ that isn't serializable as an instance variable, like a form.
75
+
76
+ ```ruby
77
+ def omitted_from_state
78
+ [:@form]
79
+ end
80
+ ```
81
+
82
+ ### reflex_tag(reflex, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
83
+ This shares the same definition as `content_tag`, except it accepts a reflex as the first parameter.
84
+
85
+ ```erb
86
+ <%= reflex_tag :increment, :button, "Click me!" %>
87
+ ```
88
+
89
+ Would add a click handler to the `increment` method on your component.
90
+
91
+ To use a non-click event, specific that with `->` notiation
92
+
93
+ ```erb
94
+ <%= reflex_tag "mouseenter->increment", :button, "Click me!" %>
95
+ ```
96
+
97
+ ### collection_key
98
+ 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.
99
+ This is used to reconcile state in the background.
100
+
101
+ ```ruby
102
+ def initialize
103
+ @my_model = MyModel.new
104
+ end
105
+
106
+ def collection_key
107
+ @my_model.id
108
+ end
109
+ ```
110
+
111
+ ### prevent_refresh!
112
+ By default, VCR will re-render your component after it executes your method. `revent_refresh!` prevents this from happening.
113
+
114
+ ```ruby
115
+ def my_method
116
+ prevent_refresh!
117
+ @foo = Lbar
118
+ end # the rendered page will not reflect this change
119
+ ```
120
+
121
+ ### refresh_all!
122
+ Refresh the entire body of the page
123
+
124
+ ```ruby
125
+ def do_some_global_action
126
+ prevent_refresh!
127
+ session[:model] = MyModel.new
128
+ refresh_all!
129
+ end
130
+ ```
131
+
132
+ ### key
133
+ 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
134
+
135
+ ```erb
136
+ <button type="button" data-reflex="click->MyComponent#do_something" data-key="<%= key %>">Click me!</button>
137
+ ```
138
+
139
+ ### component_controller(options = {}, &blk)
140
+ This is a view helper to properly connect VCR to the component. It outputs `<div data-controller="my-controller" key=<%= key %></div>`
141
+ You *must* wrap your component in this for everything to work properly.
142
+
143
+ ```erb
144
+ <%= component_controller do %>
145
+ <p><%= @count %></p
146
+ <% end %>
147
+ ```
148
+
149
+ ## Common patterns
150
+ A lot of the time, you only need to update specific components when changing instance variables. For example, changing `@loading` might only need
151
+ 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
152
+
153
+ ```ruby
154
+ def initialize
155
+ @loading = false
156
+ end
157
+
158
+ def loading=(new_value)
159
+ @loading = new_value
160
+ refresh! '#loader'
161
+ end
162
+
163
+ def do_expensive_action
164
+ prevent_refresh!
165
+
166
+ self.loading = true
167
+ execute_it
168
+ self.loading = false
169
+ end
170
+ ```
171
+
172
+ ```erb
173
+ <%= component_controller do %>
174
+ <div id="loader">
175
+ <% if @loading %>
176
+ <p>Loading...</p>
177
+ <% end %>
178
+ </div>
179
+
180
+ <button type="button" data-reflex="click->MyComponent#do_expensive_action" data-key="<%= key %>">Click me!</button>
181
+ <% end
182
+ ```
183
+
56
184
  ## Custom State Adapters
57
185
 
58
186
  ViewComponentReflex uses session for its state by default. To change this, add
@@ -76,13 +204,13 @@ class YourAdapter
76
204
  end
77
205
 
78
206
  ##
79
- # set_state is used to modify the state. It accepts a reflex, which gives you
80
- # access to the request, as well as the controller and other useful objects.
207
+ # set_state is used to modify the state.
81
208
  #
82
- # reflex - The reflex instance that's trying to set the state
209
+ # request - a rails request object
210
+ # controller - the current controller
83
211
  # key - a unique string that identifies the component
84
212
  # new_state - the new state to set
85
- def self.set_state(reflex, key, new_state)
213
+ def self.set_state(request, controller, key, new_state)
86
214
  # update the state
87
215
  end
88
216
 
@@ -43,7 +43,11 @@ module ViewComponentReflex
43
43
  component.instance_variable_set(k, v)
44
44
  end
45
45
  name.to_proc.call(component, *args)
46
- refresh!
46
+ refresh! unless @prevent_refresh
47
+ end
48
+
49
+ def prevent_refresh!
50
+ @prevent_refresh = true
47
51
  end
48
52
 
49
53
  define_method :component_class do
@@ -62,7 +66,7 @@ module ViewComponentReflex
62
66
  return @component if @component
63
67
  @component = component_class.allocate
64
68
  reflex = self
65
- exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller]
69
+ exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!]
66
70
  exposed_methods.each do |meth|
67
71
  @component.define_singleton_method(meth) do |*a|
68
72
  reflex.send(meth, *a)
@@ -72,7 +76,7 @@ module ViewComponentReflex
72
76
  end
73
77
 
74
78
  def set_state(new_state = {})
75
- ViewComponentReflex::Engine.state_adapter.set_state(self, element.dataset[:key], new_state)
79
+ ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
76
80
  end
77
81
 
78
82
  def state
@@ -98,15 +102,23 @@ module ViewComponentReflex
98
102
  helpers.controller.instance_variable_get(:@stimulus_reflex)
99
103
  end
100
104
 
101
- def component_controller(opts = {}, &blk)
105
+ def component_controller(opts_or_tag = :div, opts = {}, &blk)
102
106
  self.class.init_stimulus_reflex
103
107
  init_key
104
- opts[:data] = {
108
+
109
+ tag = :div
110
+ if opts_or_tag.is_a? Hash
111
+ options = opts_or_tag
112
+ else
113
+ tag = opts_or_tag
114
+ options = opts
115
+ end
116
+ options[:data] = {
105
117
  controller: self.class.stimulus_controller,
106
118
  key: key,
107
- **(opts[:data] || {})
119
+ **(options[:data] || {})
108
120
  }
109
- content_tag :div, capture(&blk), opts
121
+ content_tag tag, capture(&blk), options
110
122
  end
111
123
 
112
124
  # key is required if you're using state
@@ -121,10 +133,36 @@ module ViewComponentReflex
121
133
  @key = key
122
134
  end
123
135
 
136
+ def reflex_tag(reflex, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
137
+ action, method = reflex.to_s.split("->")
138
+ if method.nil?
139
+ method = action
140
+ action = "click"
141
+ end
142
+ data_attributes = {
143
+ reflex: "#{action}->#{self.class.name}##{method}",
144
+ key: key
145
+ }
146
+ if content_or_options_with_block.is_a?(Hash)
147
+ merge_data_attributes(content_or_options_with_block, data_attributes)
148
+ else
149
+ merge_data_attributes(options, data_attributes)
150
+ end
151
+ content_tag(name, content_or_options_with_block, options, escape, &block)
152
+ end
153
+
124
154
  def collection_key
125
155
  nil
126
156
  end
127
157
 
158
+ def permit_parameter?(initial_param, new_param)
159
+ initial_param != new_param
160
+ end
161
+
162
+ def omitted_from_state
163
+ []
164
+ end
165
+
128
166
  def key
129
167
  # initialize session state
130
168
  if !stimulus_reflex? || session[@key].nil?
@@ -137,15 +175,30 @@ module ViewComponentReflex
137
175
  :@helpers, :@controller, :@request, :@content
138
176
  ]
139
177
  instance_variables.reject { |k| blacklist.include?(k) }.each do |k|
140
- new_state[k] = instance_variable_get(k)
178
+ new_state[k] = instance_variable_get(k) unless omitted_from_state.include?(k)
141
179
  end
142
180
  ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
181
+ ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", new_state)
143
182
  else
183
+ initial_state = ViewComponentReflex::Engine.state_adapter.state(request, "#{@key}_initial")
144
184
  ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
145
- instance_variable_set(k, v)
185
+ unless permit_parameter?(initial_state[k], instance_variable_get(k))
186
+ instance_variable_set(k, v)
187
+ end
146
188
  end
147
189
  end
148
190
  @key
149
191
  end
192
+
193
+ private
194
+
195
+ def merge_data_attributes(options, attributes)
196
+ data = options[:data]
197
+ if data.nil?
198
+ options[:data] = attributes
199
+ else
200
+ options[:data].merge! attributes
201
+ end
202
+ end
150
203
  end
151
204
  end
@@ -5,12 +5,12 @@ module ViewComponentReflex
5
5
  request.session[key] ||= {}
6
6
  end
7
7
 
8
- def self.set_state(reflex, key, new_state)
8
+ def self.set_state(request, controller, key, new_state)
9
9
  new_state.each do |k, v|
10
- state(reflex.request, key)[k] = v
10
+ state(request, key)[k] = v
11
11
  end
12
- store = reflex.request.session.instance_variable_get("@by")
13
- store.commit_session reflex.request, reflex.controller.response
12
+ store = request.session.instance_variable_get("@by")
13
+ store.commit_session request, controller.response
14
14
  end
15
15
 
16
16
  def self.store_state(request, key, new_state = {})
@@ -1,3 +1,3 @@
1
1
  module ViewComponentReflex
2
- VERSION = '1.1.1'
2
+ VERSION = '1.6.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component_reflex
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua LeBlanc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-20 00:00:00.000000000 Z
11
+ date: 2020-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails