Class: ViewComponent::Base

Inherits:
ActionView::Base
  • Object
show all
Includes:
ActiveSupport::Configurable, Previewable
Defined in:
lib/view_component/base.rb

Direct Known Subclasses

ActionView::Component::Base

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBase

Returns a new instance of Base.



74
# File 'lib/view_component/base.rb', line 74

def initialize(*); end

Class Method Details

.call_method_name(variant) ⇒ Object



140
141
142
143
144
145
146
# File 'lib/view_component/base.rb', line 140

def call_method_name(variant)
  if variant.present? && variants.include?(variant)
    "call_#{variant}"
  else
    "call"
  end
end

.compile(raise_template_errors: false) ⇒ Object

Compile templates to instance methods, assuming they haven’t been compiled already. We could in theory do this on app boot, at least in production environments. Right now this just compiles the first time the component is rendered.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/view_component/base.rb', line 169

def compile(raise_template_errors: false)
  return if compiled?

  if template_errors.present?
    raise ViewComponent::TemplateError.new(template_errors) if raise_template_errors
    return false
  end

  templates.each do |template|
    class_eval "      def \#{call_method_name(template[:variant])}\n        @output_buffer = ActionView::OutputBuffer.new\n        \#{compiled_template(template[:path])}\n      end\n    RUBY\n  end\n\n  @compiled = true\nend\n", template[:path], -1

.compile!Object



162
163
164
# File 'lib/view_component/base.rb', line 162

def compile!
  compile(raise_template_errors: true)
end

.compiled?Boolean

Returns:

  • (Boolean)


158
159
160
# File 'lib/view_component/base.rb', line 158

def compiled?
  @compiled && ActionView::Base.cache_template_loading
end

.identifierObject



198
199
200
# File 'lib/view_component/base.rb', line 198

def identifier
  source_location
end

.inherited(child) ⇒ Object



134
135
136
137
138
# File 'lib/view_component/base.rb', line 134

def inherited(child)
  child.include Rails.application.routes.url_helpers unless child < Rails.application.routes.url_helpers

  super
end

.source_locationObject



148
149
150
151
152
153
154
155
156
# File 'lib/view_component/base.rb', line 148

def source_location
  @source_location ||=
    begin
      # Require `#initialize` to be defined so that we can use `method#source_location`
      # to look up the filename of the component.
      initialize_method = instance_method(:initialize)
      initialize_method.source_location[0] if initialize_method.owner == self
    end
end

.typeObject

we’ll eventually want to update this to support other types



194
195
196
# File 'lib/view_component/base.rb', line 194

def type
  "text/html"
end

.variantsObject



189
190
191
# File 'lib/view_component/base.rb', line 189

def variants
  templates.map { |template| template[:variant] }
end

.with_content_areas(*areas) ⇒ Object



202
203
204
205
206
207
208
# File 'lib/view_component/base.rb', line 202

def with_content_areas(*areas)
  if areas.include?(:content)
    raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
  end
  attr_reader *areas
  self.content_areas = areas
end

Instance Method Details

#before_render_checkObject



66
67
68
# File 'lib/view_component/base.rb', line 66

def before_render_check
  # noop
end

#controllerObject



84
85
86
# File 'lib/view_component/base.rb', line 84

def controller
  @controller ||= view_context.controller
end

#formatObject

:nodoc:



102
103
104
# File 'lib/view_component/base.rb', line 102

def format # :nodoc:
  @variant
end

#helpersObject

Provides a proxy to access helper methods through



89
90
91
# File 'lib/view_component/base.rb', line 89

def helpers
  @helpers ||= view_context
end

#render(options = {}, args = {}, &block) ⇒ Object



76
77
78
79
80
81
82
# File 'lib/view_component/base.rb', line 76

def render(options = {}, args = {}, &block)
  if options.is_a?(String) || (options.is_a?(Hash) && options.has_key?(:partial))
    view_context.render(options, args, &block)
  else
    super
  end
end

#render?Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/view_component/base.rb', line 70

def render?
  true
end

#render_in(view_context, &block) ⇒ Object

Entrypoint for rendering components.

view_context: ActionView context from calling view block: optional block to be captured within the view context

returns HTML that has been escaped by the respective template handler

Example subclass:

app/components/my_component.rb: class MyComponent < ViewComponent::Base

def initialize(title:)
  @title = title
end

end

app/components/my_component.html.erb <span title=“<%= @title %>”>Hello, <%= content %>!</span>

In use: <%= render MyComponent.new(title: “greeting”) do %>world<% end %> returns: <span title=“greeting”>Hello, world!</span>



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/view_component/base.rb', line 41

def render_in(view_context, &block)
  self.class.compile!
  @view_context = view_context
  @view_renderer ||= view_context.view_renderer
  @lookup_context ||= view_context.lookup_context
  @view_flow ||= view_context.view_flow
  @virtual_path ||= virtual_path
  @variant = @lookup_context.variants.first

  old_current_template = @current_template
  @current_template = self

  @content = view_context.capture(self, &block) if block_given?

  before_render_check

  if render?
    send(self.class.call_method_name(@variant))
  else
    ""
  end
ensure
  @current_template = old_current_template
end

#view_cache_dependenciesObject



98
99
100
# File 'lib/view_component/base.rb', line 98

def view_cache_dependencies
  []
end

#virtual_pathObject

Removes the first part of the path and the extension.



94
95
96
# File 'lib/view_component/base.rb', line 94

def virtual_path
  self.class.source_location.gsub(%r{(.*app/components)|(\.rb)}, "")
end

#with(area, content = nil, &block) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/view_component/base.rb', line 106

def with(area, content = nil, &block)
  unless content_areas.include?(area)
    raise ArgumentError.new "Unknown content_area '#{area}' - expected one of '#{content_areas}'"
  end

  if block_given?
    content = view_context.capture(&block)
  end

  instance_variable_set("@#{area}".to_sym, content)
  nil
end