view_component_reflex 1.7.1 → 2.1.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: c34425ccfef14458d218da7f297859dee0d17e5b922c9cefc82059e13fa5af99
4
- data.tar.gz: 6f201ab4b1a8a84e08a840a3de07e117d4c4dc1095dbe8decaf2c6b70bed971f
3
+ metadata.gz: 415195dd61211d392433d4e85963a6807871143f255b3264e2f7b4871bb42c42
4
+ data.tar.gz: 69f6030f02557d4d9886e3cfc00c738c66d748eb7a1a4b8149136ddaef8f8e9d
5
5
  SHA512:
6
- metadata.gz: d4ee24cfffed38e1e6f72c33c6aaffb3d8573328759da2be6175d85bb1d2fa1973c5314aa79ec5ed94be73655f4c42929674cff964bcc7ee0bb600db331d08bf
7
- data.tar.gz: f89ab463f3aaf436da93442ac9c212b3552367ef6a10815180142a83022944a0188c2993cfe678bd3d579b8a5d334d3bb302a44bf29e480399daa6393b0159c3
6
+ metadata.gz: e12ff695c21dc3f6c140c9d7aa7ccc854eeaeacf2a8071dc519f87c4ca1447464c01f8dfc9c200c0ac720dd864a71788fcc80f74072577c4c833743ccea29c79
7
+ data.tar.gz: ed7a85c244de733490571e7c4b529ce7b8dd295c0f6c1a5d57b15f40460b4a3737f2669bf8f28845d80a06b2ed721de49bc810c7ac9477629ead664635b2f2ee
data/README.md CHANGED
@@ -108,6 +108,33 @@ def collection_key
108
108
  end
109
109
  ```
110
110
 
111
+ ### stimulate(target, data)
112
+ Stimulate another reflex from within your component. This typically requires the key of the component you're stimulating
113
+ which can be passed in via parameters.
114
+
115
+ ```ruby
116
+ def initialize(parent_key)
117
+ @parent_key = parent_key
118
+ end
119
+
120
+ def stimulate_other
121
+ stimulate("OtherComponent#method", { key: @parent_key })
122
+ end
123
+ ```
124
+
125
+ ### refresh!(selectors)
126
+ Refresh a specific element on the page. Using this will implicitly run `prevent_render!`.
127
+ 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
128
+
129
+ ```
130
+ def my_method
131
+ refresh! '#my-special-element', selector
132
+ end
133
+ ```
134
+
135
+ ### selector
136
+ Returns the unique selector for this component. Useful to pass to `refresh!` when refreshing custom elements.
137
+
111
138
  ### prevent_refresh!
112
139
  By default, VCR will re-render your component after it executes your method. `revent_refresh!` prevents this from happening.
113
140
 
@@ -2,122 +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!(*selectors)
8
- save_state
9
-
10
- # If the component has instance variables omitted from state,
11
- # we can't render it to string from here because those instance
12
- # variables will be missing. In that case, set the selectors to the
13
- # default selector and manually morph the page
14
- if selectors.empty? && !component.can_render_to_string?
15
- selectors.push selector
16
- end
17
-
18
- # If we're just updating the component itself, we can
19
- # directly render it instead of rendering the entire page again
20
- if selectors.empty?
21
- refresh_component!
22
- else
23
- @channel.send :render_page_and_broadcast_morph, self, selectors, {
24
- "dataset" => element.dataset.to_h,
25
- "args" => [],
26
- "attrs" => element.attributes.to_h,
27
- "selectors" => ["body"],
28
- "target" => "#{self.class.name}##{method_name}",
29
- "url" => request.url,
30
- "permanent_attribute_name" => permanent_attribute_name
31
- }
32
- end
33
- end
34
-
35
- def refresh_component!
36
- # The component can't figure out the key when we render from here
37
- # Luckily we already know the key, so we can manually override it
38
- component.tap do |k|
39
- k.define_singleton_method(:key) do
40
- element.dataset[:key]
41
- end
42
- end
43
- html = controller.render_component_to_string(component)
44
- document = Nokogiri::HTML(html)
45
- morph selector, document.css("#{selector} > *").to_s
46
- end
47
-
48
- def selector
49
- "[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]"
50
- end
51
-
52
- def refresh_all!
53
- morph :body, render_page(self)
54
- end
55
-
56
- # SR's delegate_call_to_reflex in channel.rb
57
- # uses method to gather the method parameters, but since we're abusing
58
- # method_missing here, that'll always fail
59
- def method(name)
60
- name.to_sym.to_proc
61
- end
62
-
63
- def respond_to_missing?(name, _ = false)
64
- !!name.to_proc
65
- end
66
-
67
- def method_missing(name, *args)
68
- super unless respond_to_missing?(name)
69
- state.each do |k, v|
70
- component.instance_variable_set(k, v)
71
- end
72
- name.to_proc.call(component, *args)
73
- refresh! unless @prevent_refresh
74
- end
75
-
76
- def prevent_refresh!
77
- @prevent_refresh = true
78
- end
79
-
80
- define_method :component_class do
81
- @component_class ||= klass
82
- end
83
-
84
- private :component_class
85
-
86
- private
87
-
88
- def stimulus_controller
89
- component_class.stimulus_controller
90
- end
91
-
92
- def component
93
- return @component if @component
94
- @component = component_class.allocate
95
- reflex = self
96
- exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!]
97
- exposed_methods.each do |meth|
98
- @component.define_singleton_method(meth) do |*a|
99
- reflex.send(meth, *a)
100
- end
101
- end
102
- @component
103
- end
104
-
105
- def set_state(new_state = {})
106
- ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
107
- end
108
-
109
- def state
110
- ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
111
- end
112
-
113
- def save_state
114
- new_state = {}
115
- component.safe_instance_variables.each do |k|
116
- new_state[k] = component.instance_variable_get(k)
117
- end
118
- set_state(new_state)
119
- end
120
- })
5
+ @stimulus_reflex ||= Object.const_set(name + "Reflex", Class.new(Reflex))
6
+ @stimulus_reflex.component_class = self
121
7
  end
122
8
  end
123
9
 
@@ -129,10 +15,6 @@ module ViewComponentReflex
129
15
  helpers.controller.instance_variable_get(:@stimulus_reflex)
130
16
  end
131
17
 
132
- def can_render_to_string?
133
- omitted_from_state.empty?
134
- end
135
-
136
18
  def component_controller(opts_or_tag = :div, opts = {}, &blk)
137
19
  self.class.init_stimulus_reflex
138
20
  init_key
@@ -152,6 +34,10 @@ module ViewComponentReflex
152
34
  content_tag tag, capture(&blk), options
153
35
  end
154
36
 
37
+ def can_render_to_string?
38
+ omitted_from_state.empty?
39
+ end
40
+
155
41
  # key is required if you're using state
156
42
  # We can't initialize the session state in the initial method
157
43
  # because it doesn't have a view_context yet
@@ -205,7 +91,11 @@ module ViewComponentReflex
205
91
  else
206
92
  initial_state = ViewComponentReflex::Engine.state_adapter.state(request, "#{@key}_initial")
207
93
  ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
208
- unless permit_parameter?(initial_state[k], instance_variable_get(k))
94
+ instance_value = instance_variable_get(k)
95
+ if permit_parameter?(initial_state[k], instance_value)
96
+ ViewComponentReflex::Engine.state_adapter.set_state(request, controller, "#{@key}_initial", { k => instance_value })
97
+ ViewComponentReflex::Engine.state_adapter.set_state(request, controller, @key, { k => instance_value })
98
+ else
209
99
  instance_variable_set(k, v)
210
100
  end
211
101
  end
@@ -223,7 +113,7 @@ module ViewComponentReflex
223
113
  [
224
114
  :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
225
115
  :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
226
- :@helpers, :@controller, :@request, :@content, :@tag_builder
116
+ :@helpers, :@controller, :@request, :@tag_builder
227
117
  ]
228
118
  end
229
119
 
@@ -0,0 +1,140 @@
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
+ permanent_attribute_name: "data-reflex-permanent"
28
+ )
29
+ end
30
+ end
31
+ else
32
+ refresh_component!
33
+ end
34
+ cable_ready.broadcast
35
+ end
36
+
37
+ def refresh_component!
38
+ component.tap do |k|
39
+ k.define_singleton_method(:key) do
40
+ element.dataset[:key]
41
+ end
42
+ end
43
+ document = Nokogiri::HTML(controller.render_component_to_string(component))
44
+ cable_ready[channel.stream_name].morph(
45
+ selector: selector,
46
+ children_only: true,
47
+ html: document.css(selector).inner_html,
48
+ permanent_attribute_name: "data-reflex-permanent"
49
+ )
50
+ end
51
+
52
+ def refresh_all!
53
+ refresh!("body")
54
+ end
55
+
56
+ def selector
57
+ "[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]"
58
+ end
59
+
60
+ # SR's delegate_call_to_reflex in channel.rb
61
+ # uses method to gather the method parameters, but since we're abusing
62
+ # method_missing here, that'll always fail
63
+ def method(name)
64
+ name.to_sym.to_proc
65
+ end
66
+
67
+ def respond_to_missing?(name, _ = false)
68
+ !!name.to_proc
69
+ end
70
+
71
+ before_reflex do |a|
72
+ a.send a.method_name
73
+ throw :abort
74
+ end
75
+
76
+ def method_missing(name, *args)
77
+ super unless respond_to_missing?(name)
78
+ state.each do |k, v|
79
+ component.instance_variable_set(k, v)
80
+ end
81
+ name.to_proc.call(component, *args)
82
+ refresh! unless @prevent_refresh
83
+ end
84
+
85
+ def prevent_refresh!
86
+ @prevent_refresh = true
87
+ end
88
+
89
+ private
90
+
91
+ def component_class
92
+ self.class.component_class
93
+ end
94
+
95
+ def stimulus_controller
96
+ component_class.stimulus_controller
97
+ end
98
+
99
+ def stimulate(target, data)
100
+ dataset = {}
101
+ data.each do |k, v|
102
+ dataset["data-#{k}"] = v.to_s
103
+ end
104
+ channel.receive({
105
+ "target" => target,
106
+ "attrs" => element.attributes.to_h.symbolize_keys,
107
+ "dataset" => dataset
108
+ })
109
+ end
110
+
111
+ def component
112
+ return @component if @component
113
+ @component = component_class.allocate
114
+ reflex = self
115
+ exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller, :session, :prevent_refresh!, :selector, :stimulate]
116
+ exposed_methods.each do |meth|
117
+ @component.define_singleton_method(meth) do |*a|
118
+ reflex.send(meth, *a)
119
+ end
120
+ end
121
+ @component
122
+ end
123
+
124
+ def set_state(new_state = {})
125
+ ViewComponentReflex::Engine.state_adapter.set_state(request, controller, element.dataset[:key], new_state)
126
+ end
127
+
128
+ def state
129
+ ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
130
+ end
131
+
132
+ def save_state
133
+ new_state = {}
134
+ component.safe_instance_variables.each do |k|
135
+ new_state[k] = component.instance_variable_get(k)
136
+ end
137
+ set_state(new_state)
138
+ end
139
+ end
140
+ end
@@ -1,7 +1,7 @@
1
1
  require "view_component_reflex/state_adapter/session"
2
2
  require "view_component_reflex/state_adapter/memory"
3
3
  require "view_component_reflex/engine"
4
- require 'stimulus_reflex'
4
+ require "stimulus_reflex"
5
5
 
6
6
  module ViewComponentReflex
7
7
  # Your code goes here...
@@ -1,3 +1,3 @@
1
1
  module ViewComponentReflex
2
- VERSION = '1.7.1'
2
+ VERSION = '2.1.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.7.1
4
+ version: 2.1.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-07-05 00:00:00.000000000 Z
11
+ date: 2020-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -36,28 +36,28 @@ dependencies:
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 3.3.0.pre0
39
+ version: '0'
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 3.3.0.pre0
46
+ version: '0'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: view_component
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 2.13.0
53
+ version: '0'
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 2.13.0
60
+ version: '0'
61
61
  description: Allow stimulus reflexes in a view component
62
62
  email:
63
63
  - joshleblanc94@gmail.com
@@ -69,6 +69,7 @@ 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
74
75
  - lib/view_component_reflex/state_adapter/memory.rb