view_component_reflex 1.1.0 → 1.5.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: 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