view_component_reflex 0.5.1 → 1.0.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: 730dc20cb5c31f500fb8c6fd63c5e21286a4a3ea28f63a64aaa06c1640ef4844
4
- data.tar.gz: 911b8665fd95a69b28de2bf366ffa9edb4240070c8e29c145090d27ba8eaf271
3
+ metadata.gz: f7e9a4ab35ecb85fef158f336121ffd080f26d344bbf804abfc1fd85849b6734
4
+ data.tar.gz: 202ab52b2ad27cca0b823cb13d71b0389d6dba68e35e1f92da2ef601a2690125
5
5
  SHA512:
6
- metadata.gz: 679abeb33702936136cfc32244be5bd7ed85cbe8526594eb3f3ed8138b0aec7e004507eab5c4624fa0accd19958999a62641122dbfd67693ab9c3bf11424fd9f
7
- data.tar.gz: 97f3b2645a43893d09a253c3d8c780ce1c141aab599c67db3fb6bd47ed443780960d831da6b10ff2930a1bdb0eb063ff667c5292b1c23f1e6eb9f1292f890c40
6
+ metadata.gz: aeccacfe56978cc599fabdba202e645950cbf09a52c1a42bcdd12620018a2299a6858c0cda5c13ad644d0e78c766cffef55d46227874ed54d4944c0cef70f042
7
+ data.tar.gz: e5e7798417649a6233513a741f7c891e05abe15c941e2ade30bb7a9200b8c9e93039ba54a656cff05bf5a1dab886dd1dfc7bfcc4b42539853ce2cac70857c606
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
17
  # counter_component.rb
34
18
  class CounterComponent < ViewComponentReflex::Component
35
-
36
19
  def initialize
37
- initialize_state({
38
- count: 0
39
- })
20
+ @count = 0
40
21
  end
41
22
 
42
- reflex :increment do
43
- set_state(count: state[:count] + 1)
23
+ def increment
24
+ @count += 1
44
25
  end
45
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,83 +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!
16
- @channel.send :render_page_and_broadcast_morph, self, ["[data-controller=\"#{stimulus_controller}\"]"], {
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], {
17
10
  dataset: element.dataset.to_h,
18
11
  args: [],
19
12
  attrs: element.attributes.to_h,
20
- selectors: ['body'],
13
+ selectors: ["body"],
21
14
  target: "#{self.class.name}##{method_name}",
22
15
  url: request.url,
23
16
  permanentAttributeName: "data-reflex-permanent"
24
17
  }
25
18
  end
26
19
 
27
- def set_state(new_state = {})
28
- ViewComponentReflex::Engine.state_adapter.set_state(self, element.dataset[:key], new_state)
29
- refresh!
20
+ def refresh_all!
21
+ refresh!("body")
30
22
  end
31
23
 
32
- before_reflex do |reflex, *args|
33
- instance_exec(*args, &self.class.callbacks[self.method_name.to_sym]) if self.class.callbacks.include?(self.method_name.to_sym)
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
34
37
  throw :abort
35
38
  end
36
39
 
37
- def self.callbacks
38
- @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
59
+ end
60
+
61
+ def component
62
+ return @component if @component
63
+ @component = component_class.allocate
64
+ reflex = self
65
+ exposed_methods = [: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
72
+ end
73
+
74
+ def set_state(new_state = {})
75
+ ViewComponentReflex::Engine.state_adapter.set_state(self, element.dataset[:key], new_state)
39
76
  end
40
77
 
41
- define_method :stimulus_controller do
42
- klass.name.chomp("Component").underscore.dasherize
78
+ def state
79
+ ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
43
80
  end
44
81
 
45
- define_singleton_method(:reflex) do |name, &blk|
46
- callbacks[name] = blk
47
- define_method(name) do |*args|
82
+ def save_state
83
+ new_state = {}
84
+ component.instance_variables.each do |k|
85
+ new_state[k] = component.instance_variable_get(k)
48
86
  end
87
+ set_state(new_state)
49
88
  end
50
89
  })
51
90
  end
52
91
  end
53
92
 
54
- def initialize_state(obj)
55
- @state = obj
93
+ def self.stimulus_controller
94
+ name.chomp("Component").underscore.dasherize
56
95
  end
57
96
 
58
97
  def stimulus_reflex?
59
98
  helpers.controller.instance_variable_get(:@stimulus_reflex)
60
99
  end
61
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
+
62
106
  # key is required if you're using state
63
107
  # We can't initialize the session state in the initial method
64
108
  # because it doesn't have a view_context yet
65
109
  # This is the next best place to do it
66
110
  def key
67
- @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
68
118
 
69
119
  # initialize session state
70
120
  if !stimulus_reflex? || session[@key].nil?
71
- ViewComponentReflex::Engine.state_adapter.store_state(request, @key, @state)
72
- 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)
73
133
  else
74
- # 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
75
137
  end
76
138
  @key
77
139
  end
78
-
79
- def state
80
- ViewComponentReflex::Engine.state_adapter.state(request, key)
81
- end
82
140
  end
83
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.5.1'
2
+ VERSION = '1.0.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: 0.5.1
4
+ version: 1.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-11 00:00:00.000000000 Z
11
+ date: 2020-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails