view_component_reflex 0.6.3 → 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: 25f3eb9ce0b0a130b3448c9aa350636e86f4497c9274a1a4ba7edfb564d11a16
4
- data.tar.gz: 809b06a816a92e8ba1e3ce0198c55c6f3d812a0ad3c4b447300c56e4733102b4
3
+ metadata.gz: f7e9a4ab35ecb85fef158f336121ffd080f26d344bbf804abfc1fd85849b6734
4
+ data.tar.gz: 202ab52b2ad27cca0b823cb13d71b0389d6dba68e35e1f92da2ef601a2690125
5
5
  SHA512:
6
- metadata.gz: e3c0cf57dda891149ccd671d02fbc967aaa83277f789a1cd0c7c75192fa4a83e3e593177ef49f904cbc9cc8bb241b9506527bfafee0b90b049252b1822a4cb68
7
- data.tar.gz: 94c5b424ae03b1f35f797ddebf0d6f24bf78b05960140c415cce86d4c2bf645ce4eef7445d7049ef57ab0d2f26925bac251b0a202199840f23f4f0adee22fc17
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,19 +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
80
+ # replace the state
92
81
  end
93
82
  end
94
83
  ```
@@ -1,23 +1,16 @@
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!(primary_selector = "[data-controller=\"#{stimulus_controller}\"]", *selectors)
7
+ def refresh!(primary_selector = "[data-controller=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]", *selectors)
8
+ save_state
16
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"
@@ -25,60 +18,124 @@ module ViewComponentReflex
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 = {}, primary_selector = nil, *selectors)
32
- ViewComponentReflex::Engine.state_adapter.set_state(self, element.dataset[:key], new_state)
33
- refresh!(primary_selector, *selectors)
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
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
43
72
  end
44
73
 
45
- define_method :stimulus_controller do
46
- klass.name.chomp("Component").underscore.dasherize
74
+ def set_state(new_state = {})
75
+ ViewComponentReflex::Engine.state_adapter.set_state(self, element.dataset[:key], new_state)
47
76
  end
48
77
 
49
- define_singleton_method(:reflex) do |name, &blk|
50
- callbacks[name] = blk
51
- define_method(name) do |*args|
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)
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)
133
+ else
134
+ ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
135
+ instance_variable_set(k, v)
136
+ end
76
137
  end
77
138
  @key
78
139
  end
79
-
80
- def state
81
- ViewComponentReflex::Engine.state_adapter.state(request, key)
82
- end
83
140
  end
84
141
  end
@@ -1,3 +1,3 @@
1
1
  module ViewComponentReflex
2
- VERSION = '0.6.3'
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.6.3
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-15 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