view_component_reflex 0.6.0 → 1.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: f8bf2b7cc30e75bc01877f62705927b40e5f16fb7f763e065f33c857f8a6e9f2
4
- data.tar.gz: 5af71af978af81acc1eef07609937e763ead6d9bbfd89dc613017342b1ffa17d
3
+ metadata.gz: 82d4e40f0f58fd47d6c163e9054b3497eb46ec96a1e963037d96793dbea35530
4
+ data.tar.gz: d169f2e5a7816ee41f56cf033037c61ebb4085b201c85185c8df1cccadbbadb6
5
5
  SHA512:
6
- metadata.gz: 7362f2ea46bf50b44f14b186ec95375a42e2a3a582e97876ab4f468eb6a13ebf755d20472a684ff8023b6cee8063ba48409d497596b95c4c435fc584c57216d9
7
- data.tar.gz: 99be6f450fefb1bd19c5421f883b689b677d8ea6317fc140e7b01ecef9d6fcd177675509c956434506abb81c65845dc66427f1d65a577b7f0bb56a80fa67095d
6
+ metadata.gz: 61562e94b88bc140382c480f61e0b9e6afb7024177378978b14fd02a7d2658042fc530798ccafb65cea2092d0438a968b32361afde80b0fb23deea73b2475f5a
7
+ data.tar.gz: b7b4d73cca02d7a2b70d4b43570e09a2a7acede221d9516c29aa68232d421f05cb27da0a743f532369f0db068e01c30c7ed207c426e48a9b2e1fd7857383efab
data/README.md CHANGED
@@ -6,51 +6,32 @@ ViewComponentReflex allows you to write reflexes right in your view component co
6
6
 
7
7
  You can add reflexes to your component by adding inheriting from `ViewComponentReflex::Component`.
8
8
 
9
- To add a reflex to your component, use the `reflex` method.
10
-
11
- ```ruby
12
- reflex :my_cool_reflex do
13
- # do stuff
14
- refresh!
15
- end
16
- ```
17
-
18
9
  This will act as if you created a reflex with the method `my_cool_stuff`. To call this reflex, add `data-reflex="click->MyComponentReflex#my_cool_reflex"`, just like you're
19
10
  using stimulus reflex.
20
11
 
21
- #####note: A reflex will not automatically re-render the component upon its completion. A component will re-render whenever the `set_state` or `refresh!` method is called.
22
-
23
- In addition to calling reflexes, there is a rudimentary state system. You can initialize component-local state with `initialize_state(obj)`, where `obj` is a hash.
24
-
25
- You can access state with the `state` helper. See the code below for an example. Calling `set_state` will set the state,
26
- and also re-render your component.
27
-
28
- If you're using state add `data-key="<%= key %>"` to any html element using a reflex. This
29
- lets ViewComponentReflex keep track of which state belongs to which component.
30
-
12
+ ViewComponentReflex will maintain your component's instance variables between renders. You need to include `data-key=<%= key %>` on your root element, as well
13
+ as any element that stimulates a reflex. ViewComponent is inherently state-less, so the key is used to reconcile state to its respective component.
31
14
 
15
+ ### Example
32
16
  ```ruby
33
- # counter_component.rb
34
- class CounterComponent < ViewComponentReflex::Component
35
-
36
- def initialize
37
- initialize_state({
38
- count: 0
39
- })
40
- end
41
-
42
- reflex :increment do
43
- set_state(count: state[:count] + 1)
44
- end
45
- end
17
+ # counter_component.rb
18
+ class CounterComponent < ViewComponentReflex::Component
19
+ def initialize
20
+ @count = 0
21
+ end
22
+
23
+ def increment
24
+ @count += 1
25
+ end
26
+ end
46
27
  ```
47
28
 
48
29
  ```erb
49
30
  # counter_component.html.erb
50
- <div data-controller="counter">
51
- <p><%= state[:count] %></p>
31
+ <%= component_controller do %>
32
+ <p><%= @count %></p>
52
33
  <button type="button" data-reflex="click->CounterComponentReflex#increment" data-key="<%= key %>">Click</button>
53
- </div>
34
+ <% end %>
54
35
  ```
55
36
 
56
37
  ## Custom State Adapters
@@ -76,36 +57,27 @@ class YourAdapter
76
57
  end
77
58
 
78
59
  ##
60
+ # set_state is used to modify the state. It accepts a reflex, which gives you
61
+ # access to the request, as well as the controller and other useful objects.
62
+ #
79
63
  # reflex - The reflex instance that's trying to set the state
80
64
  # key - a unique string that identifies the component
81
65
  # new_state - the new state to set
82
66
  def self.set_state(reflex, key, new_state)
67
+ # update the state
83
68
  end
84
69
 
85
70
 
86
71
  ##
72
+ # store_state is used to replace the state entirely. It only accepts
73
+ # a request object, rather than a reflex because it's called from the component's
74
+ # side with the component's instance variables.
75
+ #
87
76
  # request - a rails request object
88
77
  # key - a unique string that identifies the component instance
89
78
  # new_state - a hash containing the component state
90
79
  def self.store_state(request, key, new_state = {})
91
- # store the state
92
- # this will be called twice, once with key, once with key_initial
93
- # key_initial contains the initial, unmodified state.
94
- # it should be used in reconcile_state to decide whether or not
95
- # to re-initialize the state
96
- end
97
-
98
- ##
99
- # request - a rails request object
100
- # key - a unique string that identifies the component instance
101
- # new_state - a hash containing the component state
102
- def self.reconcile_state(request, key, new_state)
103
- # The passed state should always match the initial state of the component
104
- # if it doesn't, we need to reset the state to the passed value.
105
- #
106
- # This handles cases where your initialize_state param computes some value that changes
107
- # initialize_state({ transaction: @customer.transactions.first })
108
- # if you delete the first transaction, that ^ is no longer valid. We need to update the state.
80
+ # replace the state
109
81
  end
110
82
  end
111
83
  ```
@@ -1,87 +1,141 @@
1
1
  module ViewComponentReflex
2
2
  class Component < ViewComponent::Base
3
3
  class << self
4
- def reflex(name, &blk)
5
- stimulus_reflex.reflex(name, &blk)
6
- end
7
-
8
- def stimulus_reflex
4
+ def init_stimulus_reflex
9
5
  klass = self
10
6
  @stimulus_reflex ||= Object.const_set(name + "Reflex", Class.new(StimulusReflex::Reflex) {
11
- def state
12
- ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
13
- end
14
-
15
- def refresh!(selectors = ["[data-controller=\"#{stimulus_controller}\"]"])
16
- @channel.send :render_page_and_broadcast_morph, self, selectors , {
17
- dataset: element.dataset.to_h,
18
- args: [],
19
- attrs: element.attributes.to_h,
20
- selectors: ['body'],
21
- target: "#{self.class.name}##{method_name}",
22
- url: request.url,
23
- permanentAttributeName: "data-reflex-permanent"
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"
24
17
  }
25
18
  end
26
19
 
27
20
  def refresh_all!
28
- refresh!(['body'])
21
+ refresh!("body")
29
22
  end
30
23
 
31
- def set_state(new_state = {})
32
- ViewComponentReflex::Engine.state_adapter.set_state(self, element.dataset[:key], new_state)
33
- refresh!
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
34
29
  end
35
30
 
36
- before_reflex do |reflex, *args|
37
- instance_exec(*args, &self.class.callbacks[self.method_name.to_sym]) if self.class.callbacks.include?(self.method_name.to_sym)
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
38
37
  throw :abort
39
38
  end
40
39
 
41
- def self.callbacks
42
- @callbacks ||= {}
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!
47
+ end
48
+
49
+ define_method :component_class do
50
+ @component_class ||= klass
51
+ end
52
+
53
+ private :component_class
54
+
55
+ private
56
+
57
+ def stimulus_controller
58
+ component_class.stimulus_controller
43
59
  end
44
60
 
45
- define_method :stimulus_controller do
46
- klass.name.chomp("Component").underscore.dasherize
61
+ def component
62
+ return @component if @component
63
+ @component = component_class.allocate
64
+ reflex = self
65
+ exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller]
66
+ exposed_methods.each do |meth|
67
+ @component.define_singleton_method(meth) do |*a|
68
+ reflex.send(meth, *a)
69
+ end
70
+ end
71
+ @component
47
72
  end
48
73
 
49
- define_singleton_method(:reflex) do |name, &blk|
50
- callbacks[name] = blk
51
- define_method(name) do |*args|
74
+ def set_state(new_state = {})
75
+ ViewComponentReflex::Engine.state_adapter.set_state(self, element.dataset[:key], new_state)
76
+ end
77
+
78
+ def state
79
+ ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
80
+ end
81
+
82
+ def save_state
83
+ new_state = {}
84
+ component.instance_variables.each do |k|
85
+ new_state[k] = component.instance_variable_get(k)
52
86
  end
87
+ set_state(new_state)
53
88
  end
54
89
  })
55
90
  end
56
91
  end
57
92
 
58
- def initialize_state(obj)
59
- @state = obj
93
+ def self.stimulus_controller
94
+ name.chomp("Component").underscore.dasherize
60
95
  end
61
96
 
62
97
  def stimulus_reflex?
63
98
  helpers.controller.instance_variable_get(:@stimulus_reflex)
64
99
  end
65
100
 
101
+ def component_controller(&blk)
102
+ opts = {data: {controller: self.class.stimulus_controller, key: key}}
103
+ view_context.content_tag :div, capture(&blk), opts
104
+ end
105
+
66
106
  # key is required if you're using state
67
107
  # We can't initialize the session state in the initial method
68
108
  # because it doesn't have a view_context yet
69
109
  # This is the next best place to do it
70
110
  def key
71
- @key ||= caller.find { |p| p.include? ".html.erb" }&.hash.to_s
111
+ self.class.init_stimulus_reflex
112
+ # we want the erb file that renders the component. `caller` gives the file name,
113
+ # and line number, which should be unique. We hash it to make it a nice number
114
+ key = caller.select { |p| p.include? ".html.erb" }[1]&.hash.to_s
115
+ if @key.nil? || @key.empty?
116
+ @key = key
117
+ end
72
118
 
73
119
  # initialize session state
74
120
  if !stimulus_reflex? || session[@key].nil?
75
- ViewComponentReflex::Engine.state_adapter.store_state(request, @key, @state)
76
- ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", @state)
121
+ new_state = {}
122
+
123
+ # this will almost certainly break
124
+ blacklist = [
125
+ :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
126
+ :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
127
+ :@helpers, :@controller, :@request
128
+ ]
129
+ instance_variables.reject { |k| blacklist.include?(k) }.each do |k|
130
+ new_state[k] = instance_variable_get(k)
131
+ end
132
+ ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
77
133
  else
78
- # ViewComponentReflex::Engine.state_adapter.reconcile_state(request, @key, @state)
134
+ ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
135
+ instance_variable_set(k, v)
136
+ end
79
137
  end
80
138
  @key
81
139
  end
82
-
83
- def state
84
- ViewComponentReflex::Engine.state_adapter.state(request, key)
85
- end
86
140
  end
87
141
  end
@@ -19,20 +19,6 @@ module ViewComponentReflex
19
19
  request.session[key][k] = v
20
20
  end
21
21
  end
22
-
23
- # The passed state should always match the initial state of the component
24
- # if it doesn't, we need to reset the state to the passed value.
25
- #
26
- # This handles cases where your initialize_state param computes some value that changes
27
- # initialize_state({ transaction: @customer.transactions.first })
28
- # if you delete the first transaction, that ^ is no longer valid. We need to update the state.
29
- def self.reconcile_state(request, key, new_state)
30
- request.session["#{key}_initial"].each do |k, v|
31
- if new_state[k] != v
32
- request.session[key][k] = new_state[k]
33
- end
34
- end
35
- end
36
22
  end
37
23
  end
38
24
  end
@@ -1,3 +1,3 @@
1
1
  module ViewComponentReflex
2
- VERSION = '0.6.0'
2
+ VERSION = '1.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: 0.6.0
4
+ version: 1.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-11 00:00:00.000000000 Z
11
+ date: 2020-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails