view_component_reflex 1.6.1 → 2.0.1

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: 2269d8639ced76028ffe2a5970960d7381c862a75ce36481a84d8cf8886acd6c
4
- data.tar.gz: c368a487c3a2d97b4207ecb47558877596f9f9dccc7aa63f363598bc72a4eb82
3
+ metadata.gz: 01c4ace6204896ecf089f5a494cad173b1bf73c1f5818bd5745dbaf976c3adec
4
+ data.tar.gz: 24d655b966c025128802e4f2223cf7e324f4d15ecfc7c3554bd6cdeb10224aad
5
5
  SHA512:
6
- metadata.gz: 4822e9370947d8881b8f33ca8a00df19a9458ca31f9da4bc84bcfb438e768f37226a2790f489efe94ec36418f77ed4d32a122a003653ec3120ddc49df5af2a1d
7
- data.tar.gz: 0f38085c800aa78f43e6df67cec9664115fed2cbce0cefc7fb89dbe2a0b3f624d7adfff1a71e542039af40a6b247744677e96a0115afe6f5f5ad65fb4c2a78df
6
+ metadata.gz: 47efa7af3e0dbb78e86e5dd513e79dc3722279b69204c5a4c7385da717e6207b20587dc3e680659f48d56e19afa0e01800614a2c3a6f5f5672b426d40019d374
7
+ data.tar.gz: c7e0a43ac12fa4bf42615c9469b108945b59a2d9f8b8c0890f772c8b2ca193cf5551c771f064df8ac7f200a80293795dd35476929f6543f21b7f2a98e52dd127
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
@@ -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, :@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,125 @@
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].morph(
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
+ cable_ready.broadcast
34
+ end
35
+
36
+ def refresh_component!
37
+ component.tap do |k|
38
+ k.define_singleton_method(:key) do
39
+ element.dataset[:key]
40
+ end
41
+ end
42
+ cable_ready[channel.stream_name].morph(
43
+ selector: selector,
44
+ children_only: false,
45
+ html: controller.render_component_to_string(component)
46
+ )
47
+ end
48
+
49
+ def refresh_all!
50
+ refresh!("body")
51
+ end
52
+
53
+ def selector
54
+ "[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]"
55
+ end
56
+
57
+ # SR's delegate_call_to_reflex in channel.rb
58
+ # uses method to gather the method parameters, but since we're abusing
59
+ # method_missing here, that'll always fail
60
+ def method(name)
61
+ name.to_sym.to_proc
62
+ end
63
+
64
+ def respond_to_missing?(name, _ = false)
65
+ !!name.to_proc
66
+ end
67
+
68
+ before_reflex do |a|
69
+ a.send a.method_name
70
+ throw :abort
71
+ end
72
+
73
+ def method_missing(name, *args)
74
+ super unless respond_to_missing?(name)
75
+ state.each do |k, v|
76
+ component.instance_variable_set(k, v)
77
+ end
78
+ name.to_proc.call(component, *args)
79
+ refresh! unless @prevent_refresh
80
+ end
81
+
82
+ def prevent_refresh!
83
+ @prevent_refresh = true
84
+ end
85
+
86
+ private
87
+
88
+ def component_class
89
+ self.class.component_class
90
+ end
91
+
92
+ def stimulus_controller
93
+ component_class.stimulus_controller
94
+ end
95
+
96
+ def component
97
+ return @component if @component
98
+ @component = component_class.allocate
99
+ reflex = self
100
+ exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!, :selector]
101
+ exposed_methods.each do |meth|
102
+ @component.define_singleton_method(meth) do |*a|
103
+ reflex.send(meth, *a)
104
+ end
105
+ end
106
+ @component
107
+ end
108
+
109
+ def set_state(new_state = {})
110
+ ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
111
+ end
112
+
113
+ def state
114
+ ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
115
+ end
116
+
117
+ def save_state
118
+ new_state = {}
119
+ component.safe_instance_variables.each do |k|
120
+ new_state[k] = component.instance_variable_get(k)
121
+ end
122
+ set_state(new_state)
123
+ end
124
+ end
125
+ 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
1
  module ViewComponentReflex
2
- VERSION = '1.6.1'
2
+ VERSION = '2.0.1'
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.6.1
4
+ version: 2.0.1
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-26 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