view_component_reflex 1.6.0 → 2.0.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: 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