diff --git a/README.md b/README.md
index 0434ed8..75ebd6d 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ using stimulus reflex.
ViewComponentReflex will maintain your component's instance variables between renders. You need to include `data-key=<%= key %>` on your root element, as well
as any element that stimulates a reflex. ViewComponent is inherently state-less, so the key is used to reconcile state to its respective component.
-### Example
+### example
```ruby
# counter_component.rb
class CounterComponent < ViewComponentReflex::Component
@@ -28,10 +28,8 @@ end
```erb
# counter_component.html.erb
-<%= component_controller do %>
-
+<%= reflex_tag :increment, :button, "Click" %>
```
## Collections
@@ -136,14 +134,15 @@ This is a key unique to a particular component. It's used to reconcile state bet
```
-### component_controller(options = {}, &blk)
-This is a view helper to properly connect VCR to the component. It outputs ``
-You *must* wrap your component in this for everything to work properly.
+### component_controller(opts_or_tag = :div, opts = {}, &blk)
+
+The rendered componentis automatically wrapped in a div to properly connect VCR to the component. If you want more control, you can use this helper in your view.
```erb
-<%= component_controller do %>
-
<%= @count %>
+ <%= @count %>
<% end %>
+
I won't be touched by updates
```
## Common patterns
@@ -170,14 +169,13 @@ end
```
```erb
-<%= component_controller do %>
-
- <% if @loading %>
-
Loading...
- <% end %>
-
-
-
+
+ <% if @loading %>
+
Loading...
+ <% end %>
+
+
+
<% end
```
diff --git a/app/components/view_component_reflex/component.rb b/app/components/view_component_reflex/component.rb
index adcd6cf..af3d781 100644
--- a/app/components/view_component_reflex/component.rb
+++ b/app/components/view_component_reflex/component.rb
@@ -1,5 +1,6 @@
module ViewComponentReflex
class Component < ViewComponent::Base
+ attr_reader :key
class << self
def init_stimulus_reflex
klass = self
@@ -94,18 +95,37 @@ def save_state
end
end
+ def render_in(view_context, &block)
+ @view_context = view_context
+ init_with_view_context # to get the key and the instance variables we require the view_context
+ rendered = super # we call render to see if component_controller helper is being used
+ if @component_controller_used
+ rendered
+ else
+ content_tag(:div, data: {controller: self.class.stimulus_controller, key: key}) { rendered }
+ end
+ end
+
+ def init_with_view_context
+ self.class.init_stimulus_reflex
+ init_key
+ if !stimulus_reflex? || session[@key].nil?
+ store_instance_variables
+ else
+ load_instance_variables
+ end
+ end
+
def self.stimulus_controller
name.chomp("Component").underscore.dasherize
end
def stimulus_reflex?
- helpers.controller.instance_variable_get(:@stimulus_reflex)
+ view_context.instance_variable_get(:@stimulus_reflex)
end
def component_controller(opts_or_tag = :div, opts = {}, &blk)
- self.class.init_stimulus_reflex
- init_key
-
+ @component_controller_used = true
tag = :div
if opts_or_tag.is_a? Hash
options = opts_or_tag
@@ -121,18 +141,6 @@ def component_controller(opts_or_tag = :div, opts = {}, &blk)
content_tag tag, capture(&blk), options
end
- # key is required if you're using state
- # We can't initialize the session state in the initial method
- # because it doesn't have a view_context yet
- # This is the next best place to do it
- def init_key
- # we want the erb file that renders the component. `caller` gives the file name,
- # and line number, which should be unique. We hash it to make it a nice number
- key = caller.select { |p| p.include? ".html.erb" }[1]&.hash.to_s
- key += collection_key.to_s if collection_key
- @key = key
- end
-
def reflex_tag(reflex, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
action, method = reflex.to_s.split("->")
if method.nil?
@@ -163,35 +171,43 @@ def omitted_from_state
[]
end
- def key
- # initialize session state
- if !stimulus_reflex? || session[@key].nil?
- new_state = {}
- # this will almost certainly break
- blacklist = [
+
+ private
+
+ def init_key
+ return @key if @key.present?
+
+ key = caller.select { |p| p.include? ".html.erb" }[0]&.hash.to_s
+ key += collection_key.to_s if collection_key
+ @key = key
+ end
+
+ def load_instance_variables
+ initial_state = ViewComponentReflex::Engine.state_adapter.state(request, "#{@key}_initial")
+ ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
+ unless permit_parameter?(initial_state[k], instance_variable_get(k))
+ instance_variable_set(k, v)
+ end
+ end
+ end
+
+ def store_instance_variables
+ new_state = {}
+
+ # this will almost certainly break
+ blacklist = [
:@view_context, :@lookup_context, :@view_renderer, :@view_flow,
:@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
:@helpers, :@controller, :@request, :@content
- ]
- instance_variables.reject { |k| blacklist.include?(k) }.each do |k|
- new_state[k] = instance_variable_get(k) unless omitted_from_state.include?(k)
- end
- ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
- ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", new_state)
- else
- initial_state = ViewComponentReflex::Engine.state_adapter.state(request, "#{@key}_initial")
- ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
- unless permit_parameter?(initial_state[k], instance_variable_get(k))
- instance_variable_set(k, v)
- end
- end
+ ]
+ instance_variables.reject { |k| blacklist.include?(k) }.each do |k|
+ new_state[k] = instance_variable_get(k) unless omitted_from_state.include?(k)
end
- @key
+ ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
+ ViewComponentReflex::Engine.state_adapter.store_state(request, "#{@key}_initial", new_state)
end
- private
-
def merge_data_attributes(options, attributes)
data = options[:data]
if data.nil?