view_component_reflex 1.6.1 → 2.0.1

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: 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