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(request, controller, 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



124
125
126
# File 'app/components/view_component_reflex/component.rb', line 124

def collection_key
  nil
end

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



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

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

#init_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



116
117
118
119
120
121
122
# File 'app/components/view_component_reflex/component.rb', line 116

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

#keyObject



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/components/view_component_reflex/component.rb', line 132

def key
  # 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, :@content
    ]
    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)
    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
  end
  @key
end

#permit_parameter?(initial_param, new_param) ⇒ Boolean

Returns:

  • (Boolean)


128
129
130
# File 'app/components/view_component_reflex/component.rb', line 128

def permit_parameter?(initial_param, new_param)
  initial_param != new_param
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