view_component_reflex 1.1.0 → 1.5.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: 42797141d9ed8ecfb013f8f68c9e073b9ee6db4fe4eb04f1c7484babbd2c4e06
4
- data.tar.gz: 9dfb73b083e7e1ac3417a676f37962c1861435310e0f04934d16cea5824f6eb3
3
+ metadata.gz: aed815b44b0d4708ba9bb85e9a08912f15f4d66442432df5d0933866f66e3aba
4
+ data.tar.gz: 96262b95434700007b142be8411d115dc50cdd0d5b94baf5200b582317ce858b
5
5
  SHA512:
6
- metadata.gz: 4505fedd6b66cf2e5b31fe3cdeb8ec3b337a0c6b8de79082926d637c39dd74770b562280ae6f61bc79f434b2184e993c706dcd7fac088d436b185e56228f7d28
7
- data.tar.gz: ed2b650393469281ecd997a7589f6e9999a1aeda25e624193c54c61c1d7e7b78b012718945c409c5db8db86588a7ea64e1be547a4d351e92b377329eef1d7e18
6
+ metadata.gz: 8cdd11ece267f4cc206af3e12d3e62b4881fdde56afb28a23b078437448842eb54e7aa1da43a68dd4f071544bb360e95c15db2b4ea4599da9ee7e57e7887a2d8
7
+ data.tar.gz: 64c8053422748319fd6460881170c3b5a9b84da0577180ebbf8a3dd3e62e5b1563c5d9e17c617de798141466f85894b42b281377aa553ff9a3840d9c522564d8
data/README.md CHANGED
@@ -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
@@ -100,6 +104,7 @@ module ViewComponentReflex
100
104
 
101
105
  def component_controller(opts = {}, &blk)
102
106
  self.class.init_stimulus_reflex
107
+ init_key
103
108
  opts[:data] = {
104
109
  controller: self.class.stimulus_controller,
105
110
  key: key,
@@ -108,23 +113,49 @@ module ViewComponentReflex
108
113
  content_tag :div, capture(&blk), opts
109
114
  end
110
115
 
111
- def collection_key
112
- nil
113
- end
114
-
115
116
  # key is required if you're using state
116
117
  # We can't initialize the session state in the initial method
117
118
  # because it doesn't have a view_context yet
118
119
  # This is the next best place to do it
119
- def key
120
+ def init_key
120
121
  # we want the erb file that renders the component. `caller` gives the file name,
121
122
  # and line number, which should be unique. We hash it to make it a nice number
122
123
  key = caller.select { |p| p.include? ".html.erb" }[1]&.hash.to_s
123
124
  key += collection_key.to_s if collection_key
124
- if @key.nil? || @key.empty?
125
- @key = key
125
+ @key = key
126
+ end
127
+
128
+ def reflex_tag(reflex, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
129
+ action, method = reflex.split("->")
130
+ if method.nil?
131
+ method = action
132
+ action = "click"
126
133
  end
134
+ data_attributes = {
135
+ reflex: "#{action}->#{self.class.name}##{method}",
136
+ key: key
137
+ }
138
+ if content_or_options_with_block.is_a?(Hash)
139
+ merge_data_attributes(content_or_options_with_block, data_attributes)
140
+ else
141
+ merge_data_attributes(options, data_attributes)
142
+ end
143
+ content_tag(name, content_or_options_with_block, options, escape, &block)
144
+ end
145
+
146
+ def collection_key
147
+ nil
148
+ end
149
+
150
+ def permit_parameter?(initial_param, new_param)
151
+ initial_param != new_param
152
+ end
153
+
154
+ def omitted_from_state
155
+ []
156
+ end
127
157
 
158
+ def key
128
159
  # initialize session state
129
160
  if !stimulus_reflex? || session[@key].nil?
130
161
  new_state = {}
@@ -133,18 +164,33 @@ module ViewComponentReflex
133
164
  blacklist = [
134
165
  :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
135
166
  :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
136
- :@helpers, :@controller, :@request
167
+ :@helpers, :@controller, :@request, :@content
137
168
  ]
138
169
  instance_variables.reject { |k| blacklist.include?(k) }.each do |k|
139
- new_state[k] = instance_variable_get(k)
170
+ new_state[k] = instance_variable_get(k) unless omitted_from_state.include?(k)
140
171
  end
141
172
  ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
173
+ ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", new_state)
142
174
  else
175
+ initial_state = ViewComponentReflex::Engine.state_adapter.state(request, "#{@key}_initial")
143
176
  ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
144
- instance_variable_set(k, v)
177
+ unless permit_parameter?(initial_state[k], instance_variable_get(k))
178
+ instance_variable_set(k, v)
179
+ end
145
180
  end
146
181
  end
147
182
  @key
148
183
  end
184
+
185
+ private
186
+
187
+ def merge_data_attributes(options, attributes)
188
+ data = options[:data]
189
+ if data.nil?
190
+ options[:data] = attributes
191
+ else
192
+ options[:data].merge! attributes
193
+ end
194
+ end
149
195
  end
150
196
  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.0'
2
+ VERSION = '1.5.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.0
4
+ version: 1.5.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-19 00:00:00.000000000 Z
11
+ date: 2020-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails