view_component_reflex 1.6.0 → 2.0.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: 0d42e80a31d1a4a27c828f02fc584e9c5eb6660a6d5697e0db3a9631be358dec
4
- data.tar.gz: 96450604f1d0965c104171a8b1d667f6d4dc38019e0c6434fd974ff80a3d9f26
3
+ metadata.gz: 2a3e7ed47e432ef756d8359ace66447aedd995d240eb423007ec868c9dfa0402
4
+ data.tar.gz: bb836fc180d285454b516ced91b92cc309320d9257c5da71a17c2789631fcd0f
5
5
  SHA512:
6
- metadata.gz: deaf8859ae5816796b965ba5c7e1022032253f44fc1e6c94bcdda8dd481d20afb1319a63c99acc98e184908c5c0b299384e1b77cecef4cd5099263bc4f0d4606
7
- data.tar.gz: 6da8e3668c5cf6f2dd64c770eada3b381669dd2dac2cf11dc65b2f9e7a94d4e7d450a563506459057642d0f97629d582d1935a9eef7bbf61acfc084402b2e1cd
6
+ metadata.gz: 594bf37cfa1ef0798df664220190d694d3c164ee25516b0220ba6f884b4bf3667a52cfb0668d7d0ae2d684d17c18296862f5ae2473ace3b23ac8496f29825ba6
7
+ data.tar.gz: d82970ac5abd4aea2ffb675f261ba7c6b840e1f866f9b93b51c368cead4fff06d9ac1e7c8211f679f0f58a4d6b78557fe2c553b66bec982ddaf7d1cf0c5e8433
data/README.md CHANGED
@@ -70,8 +70,8 @@ end
70
70
  ```
71
71
 
72
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.
73
+ Return an array of instance variables you want to omit from state. Only really useful if you're using the session state
74
+ adapter, and you have an instance variable that can't be serialized.
75
75
 
76
76
  ```ruby
77
77
  def omitted_from_state
@@ -108,6 +108,19 @@ def collection_key
108
108
  end
109
109
  ```
110
110
 
111
+ ### refresh!(selectors)
112
+ Refresh a specific element on the page. Using this will implicitly run `prevent_render!`.
113
+ 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
114
+
115
+ ```
116
+ def my_method
117
+ refresh! '#my-special-element', selector
118
+ end
119
+ ```
120
+
121
+ ### selector
122
+ Returns the unique selector for this component. Useful to pass to `refresh!` when refreshing custom elements.
123
+
111
124
  ### prevent_refresh!
112
125
  By default, VCR will re-render your component after it executes your method. `revent_refresh!` prevents this from happening.
113
126
 
@@ -181,6 +194,11 @@ end
181
194
  <% end
182
195
  ```
183
196
 
197
+ ## State
198
+
199
+ By default, view_component_reflex stores component state in memory. You can optionally set the state adapter
200
+ to use the session by changing `config.state_adapter` to `ViewComponentReflex::StateAdapter::Session`
201
+
184
202
  ## Custom State Adapters
185
203
 
186
204
  ViewComponentReflex uses session for its state by default. To change this, add
@@ -2,95 +2,8 @@ module ViewComponentReflex
2
2
  class Component < ViewComponent::Base
3
3
  class << self
4
4
  def init_stimulus_reflex
5
- klass = self
6
- @stimulus_reflex ||= Object.const_set(name + "Reflex", Class.new(StimulusReflex::Reflex) {
7
- def refresh!(primary_selector = "[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]", *selectors)
8
- save_state
9
- @channel.send :render_page_and_broadcast_morph, self, [primary_selector, *selectors], {
10
- "dataset" => element.dataset.to_h,
11
- "args" => [],
12
- "attrs" => element.attributes.to_h,
13
- "selectors" => ["body"],
14
- "target" => "#{self.class.name}##{method_name}",
15
- "url" => request.url,
16
- "permanent_attribute_name" => "data-reflex-permanent"
17
- }
18
- end
19
-
20
- def refresh_all!
21
- refresh!("body")
22
- end
23
-
24
- # SR's delegate_call_to_reflex in channel.rb
25
- # uses method to gather the method parameters, but since we're abusing
26
- # method_missing here, that'll always fail
27
- def method(name)
28
- name.to_sym.to_proc
29
- end
30
-
31
- def respond_to_missing?(name, _ = false)
32
- !!name.to_proc
33
- end
34
-
35
- before_reflex do |a|
36
- a.send a.method_name
37
- throw :abort
38
- end
39
-
40
- def method_missing(name, *args)
41
- super unless respond_to_missing?(name)
42
- state.each do |k, v|
43
- component.instance_variable_set(k, v)
44
- end
45
- name.to_proc.call(component, *args)
46
- refresh! unless @prevent_refresh
47
- end
48
-
49
- def prevent_refresh!
50
- @prevent_refresh = true
51
- end
52
-
53
- define_method :component_class do
54
- @component_class ||= klass
55
- end
56
-
57
- private :component_class
58
-
59
- private
60
-
61
- def stimulus_controller
62
- component_class.stimulus_controller
63
- end
64
-
65
- def component
66
- return @component if @component
67
- @component = component_class.allocate
68
- reflex = self
69
- exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!]
70
- exposed_methods.each do |meth|
71
- @component.define_singleton_method(meth) do |*a|
72
- reflex.send(meth, *a)
73
- end
74
- end
75
- @component
76
- end
77
-
78
- def set_state(new_state = {})
79
- ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
80
- end
81
-
82
- def state
83
- ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
84
- end
85
-
86
- def save_state
87
- new_state = {}
88
- component.instance_variables.each do |k|
89
- new_state[k] = component.instance_variable_get(k)
90
- end
91
- set_state(new_state)
92
- end
93
- })
5
+ @stimulus_reflex ||= Object.const_set(name + "Reflex", Class.new(Reflex))
6
+ @stimulus_reflex.component_class = self
94
7
  end
95
8
  end
96
9
 
@@ -121,6 +34,10 @@ module ViewComponentReflex
121
34
  content_tag tag, capture(&blk), options
122
35
  end
123
36
 
37
+ def can_render_to_string?
38
+ omitted_from_state.empty?
39
+ end
40
+
124
41
  # key is required if you're using state
125
42
  # We can't initialize the session state in the initial method
126
43
  # because it doesn't have a view_context yet
@@ -133,7 +50,7 @@ module ViewComponentReflex
133
50
  @key = key
134
51
  end
135
52
 
136
- def reflex_tag(reflex, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
53
+ def reflex_tag(reflex, name, content_or_options_with_block = {}, options = {}, escape = true, &block)
137
54
  action, method = reflex.to_s.split("->")
138
55
  if method.nil?
139
56
  method = action
@@ -165,18 +82,10 @@ module ViewComponentReflex
165
82
 
166
83
  def key
167
84
  # initialize session state
168
- if !stimulus_reflex? || session[@key].nil?
169
- new_state = {}
170
-
171
- # this will almost certainly break
172
- blacklist = [
173
- :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
174
- :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
175
- :@helpers, :@controller, :@request, :@content
176
- ]
177
- instance_variables.reject { |k| blacklist.include?(k) }.each do |k|
178
- new_state[k] = instance_variable_get(k) unless omitted_from_state.include?(k)
179
- end
85
+ if !stimulus_reflex? || ViewComponentReflex::Engine.state_adapter.state(request, @key).empty?
86
+
87
+ new_state = create_safe_state
88
+
180
89
  ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
181
90
  ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", new_state)
182
91
  else
@@ -190,8 +99,30 @@ module ViewComponentReflex
190
99
  @key
191
100
  end
192
101
 
102
+ def safe_instance_variables
103
+ instance_variables - unsafe_instance_variables
104
+ end
105
+
193
106
  private
194
107
 
108
+ def unsafe_instance_variables
109
+ [
110
+ :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
111
+ :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
112
+ :@helpers, :@controller, :@request, :@content, :@tag_builder
113
+ ]
114
+ end
115
+
116
+ def create_safe_state
117
+ new_state = {}
118
+
119
+ # this will almost certainly break
120
+ safe_instance_variables.each do |k|
121
+ new_state[k] = instance_variable_get(k) unless omitted_from_state.include?(k)
122
+ end
123
+ new_state
124
+ end
125
+
195
126
  def merge_data_attributes(options, attributes)
196
127
  data = options[:data]
197
128
  if data.nil?
@@ -0,0 +1,123 @@
1
+ module ViewComponentReflex
2
+ class Reflex < StimulusReflex::Reflex
3
+ include CableReady::Broadcaster
4
+
5
+ class << self
6
+ attr_accessor :component_class
7
+ end
8
+
9
+ def refresh!(primary_selector = nil, *rest)
10
+ save_state
11
+
12
+ if primary_selector.nil? && !component.can_render_to_string?
13
+ primary_selector = selector
14
+ end
15
+ if primary_selector
16
+ prevent_refresh!
17
+
18
+ controller.process(url_params[:action])
19
+ document = Nokogiri::HTML(controller.response.body)
20
+ [primary_selector, *rest].each do |s|
21
+ html = document.css(s)
22
+ if html.present?
23
+ cable_ready[channel.stream_name].inner_html(
24
+ selector: s,
25
+ html: html.inner_html,
26
+ children_only: true
27
+ )
28
+ end
29
+ end
30
+ else
31
+ refresh_component!
32
+ end
33
+ end
34
+
35
+ def refresh_component!
36
+ component.tap do |k|
37
+ k.define_singleton_method(:key) do
38
+ element.dataset[:key]
39
+ end
40
+ end
41
+ cable_ready[channel.stream_name].outer_html(
42
+ selector: selector,
43
+ html: controller.render_component_to_string(component)
44
+ )
45
+ end
46
+
47
+ def refresh_all!
48
+ refresh!("body")
49
+ end
50
+
51
+ def selector
52
+ "[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]"
53
+ end
54
+
55
+ # SR's delegate_call_to_reflex in channel.rb
56
+ # uses method to gather the method parameters, but since we're abusing
57
+ # method_missing here, that'll always fail
58
+ def method(name)
59
+ name.to_sym.to_proc
60
+ end
61
+
62
+ def respond_to_missing?(name, _ = false)
63
+ !!name.to_proc
64
+ end
65
+
66
+ before_reflex do |a|
67
+ a.send a.method_name
68
+ throw :abort
69
+ end
70
+
71
+ def method_missing(name, *args)
72
+ super unless respond_to_missing?(name)
73
+ state.each do |k, v|
74
+ component.instance_variable_set(k, v)
75
+ end
76
+ name.to_proc.call(component, *args)
77
+ refresh! unless @prevent_refresh
78
+ end
79
+
80
+ def prevent_refresh!
81
+ @prevent_refresh = true
82
+ end
83
+
84
+ private
85
+
86
+ def component_class
87
+ self.class.component_class
88
+ end
89
+
90
+ def stimulus_controller
91
+ component_class.stimulus_controller
92
+ end
93
+
94
+ def component
95
+ return @component if @component
96
+ @component = component_class.allocate
97
+ reflex = self
98
+ exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!, :selector]
99
+ exposed_methods.each do |meth|
100
+ @component.define_singleton_method(meth) do |*a|
101
+ reflex.send(meth, *a)
102
+ end
103
+ end
104
+ @component
105
+ end
106
+
107
+ def set_state(new_state = {})
108
+ ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
109
+ end
110
+
111
+ def state
112
+ ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
113
+ end
114
+
115
+ def save_state
116
+ new_state = {}
117
+ component.safe_instance_variables.each do |k|
118
+ new_state[k] = component.instance_variable_get(k)
119
+ end
120
+ set_state(new_state)
121
+ end
122
+ end
123
+ end
@@ -1,7 +1,8 @@
1
- require "view_component_reflex/state_adapter/session"
2
- require "view_component_reflex/engine"
3
- require 'stimulus_reflex'
4
-
5
- module ViewComponentReflex
6
- # Your code goes here...
7
- end
1
+ require "view_component_reflex/state_adapter/session"
2
+ require "view_component_reflex/state_adapter/memory"
3
+ require "view_component_reflex/engine"
4
+ require "stimulus_reflex"
5
+
6
+ module ViewComponentReflex
7
+ # Your code goes here...
8
+ end
@@ -1,13 +1,13 @@
1
- module ViewComponentReflex
2
- class Engine < ::Rails::Engine
3
- class << self
4
- mattr_accessor :state_adapter
5
-
6
- self.state_adapter = StateAdapter::Session
7
- end
8
-
9
- def self.configure
10
- yield self if block_given?
11
- end
12
- end
13
- end
1
+ module ViewComponentReflex
2
+ class Engine < ::Rails::Engine
3
+ class << self
4
+ mattr_accessor :state_adapter
5
+
6
+ self.state_adapter = StateAdapter::Memory
7
+ end
8
+
9
+ def self.configure
10
+ yield self if block_given?
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ VIEW_COMPONENT_REFLEX_MEMORY_STATE = {}
2
+ module ViewComponentReflex
3
+ module StateAdapter
4
+ class Memory
5
+ def self.state(request, key)
6
+ VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s] ||= {}
7
+ VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s][key] ||= {}
8
+ end
9
+
10
+ def self.set_state(request, _, key, new_state)
11
+ new_state.each do |k, v|
12
+ state(request, key)[k] = v
13
+ end
14
+ end
15
+
16
+ def self.store_state(request, key, new_state = {})
17
+ VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s] ||= {}
18
+ VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s][key] ||= {}
19
+ new_state.each do |k, v|
20
+ VIEW_COMPONENT_REFLEX_MEMORY_STATE[request.session.id.to_s][key][k] = v
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
- module ViewComponentReflex
2
- VERSION = '1.6.0'
3
- end
1
+ module ViewComponentReflex
2
+ VERSION = '2.0.0'
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.6.0
4
+ version: 2.0.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-25 00:00:00.000000000 Z
11
+ date: 2020-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -69,8 +69,10 @@ files:
69
69
  - README.md
70
70
  - Rakefile
71
71
  - app/components/view_component_reflex/component.rb
72
+ - app/reflexes/view_component_reflex/reflex.rb
72
73
  - lib/view_component_reflex.rb
73
74
  - lib/view_component_reflex/engine.rb
75
+ - lib/view_component_reflex/state_adapter/memory.rb
74
76
  - lib/view_component_reflex/state_adapter/session.rb
75
77
  - lib/view_component_reflex/version.rb
76
78
  homepage: https://github.com/joshleblanc/view_component_reflex