Class: ViewComponentReflex::Component

Inherits:
ViewComponent::Base
  • Object
show all
Defined in:
app/components/view_component_reflex/component.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.init_stimulus_reflexObject



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'app/components/view_component_reflex/component.rb', line 4

def init_stimulus_reflex
  klass = self
  @stimulus_reflex ||= Object.const_set(name + "Reflex", Class.new(StimulusReflex::Reflex) {
    def refresh!(primary_selector = "[data-controller~=\"#{stimulus_controller}\"][data-key=\"#{element.dataset[:key]}\"]", *selectors)
      save_state
      @channel.send :render_page_and_broadcast_morph, self, [primary_selector, *selectors], {
        "dataset" => element.dataset.to_h,
        "args" => [],
        "attrs" => element.attributes.to_h,
        "selectors" => ["body"],
        "target" => "#{self.class.name}##{method_name}",
        "url" => request.url,
        "permanent_attribute_name" => "data-reflex-permanent"
      }
    end

    def refresh_all!
      refresh!("body")
    end

    # SR's delegate_call_to_reflex in channel.rb
    # uses method to gather the method parameters, but since we're abusing
    # method_missing here, that'll always fail
    def method(name)
      name.to_sym.to_proc
    end

    def respond_to_missing?(name, _ = false)
      !!name.to_proc
    end

    before_reflex do |a|
      a.send a.method_name
      throw :abort
    end

    def method_missing(name, *args)
      super unless respond_to_missing?(name)
      state.each do |k, v|
        component.instance_variable_set(k, v)
      end
      name.to_proc.call(component, *args)
      refresh!
    end

    define_method :component_class do
      @component_class ||= klass
    end

    private :component_class

    private

    def stimulus_controller
      component_class.stimulus_controller
    end

    def component
      return @component if @component
      @component = component_class.allocate
      reflex = self
      exposed_methods = [:params, :request, :element, :refresh!, :refresh_all!, :stimulus_controller]
      exposed_methods.each do |meth|
        @component.define_singleton_method(meth) do |*a|
          reflex.send(meth, *a)
        end
      end
      @component
    end

    def set_state(new_state = {})
      ViewComponentReflex::Engine.state_adapter.set_state(self, element.dataset[:key], new_state)
    end

    def state
      ViewComponentReflex::Engine.state_adapter.state(request, element.dataset[:key])
    end

    def save_state
      new_state = {}
      component.instance_variables.each do |k|
        new_state[k] = component.instance_variable_get(k)
      end
      set_state(new_state)
    end
  })
end

.stimulus_controllerObject



93
94
95
# File 'app/components/view_component_reflex/component.rb', line 93

def self.stimulus_controller
  name.chomp("Component").underscore.dasherize
end

Instance Method Details

#collection_keyObject



111
112
113
# File 'app/components/view_component_reflex/component.rb', line 111

def collection_key
  nil
end

#component_controller(opts = {}, &blk) ⇒ Object



101
102
103
104
105
106
107
108
109
# File 'app/components/view_component_reflex/component.rb', line 101

def component_controller(opts = {}, &blk)
  self.class.init_stimulus_reflex
  opts[:data] = {
    controller: self.class.stimulus_controller,
    key: key,
    **(opts[:data] || {})
  }
   :div, capture(&blk), opts
end

#keyObject

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



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'app/components/view_component_reflex/component.rb', line 119

def 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
  if @key.nil? || @key.empty?
    @key = key
  end

  # initialize session state
  if !stimulus_reflex? || session[@key].nil?
    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
    ]
    instance_variables.reject { |k| blacklist.include?(k) }.each do |k|
      new_state[k] = instance_variable_get(k)
    end
    ViewComponentReflex::Engine.state_adapter.store_state(request, @key, new_state)
  else
    ViewComponentReflex::Engine.state_adapter.state(request, @key).each do |k, v|
      instance_variable_set(k, v)
    end
  end
  @key
end

#stimulus_reflex?Boolean

Returns:

  • (Boolean)


97
98
99
# File 'app/components/view_component_reflex/component.rb', line 97

def stimulus_reflex?
  helpers.controller.instance_variable_get(:@stimulus_reflex)
end