Class: Bridgetown::Component
- Inherits:
-
Object
- Object
- Bridgetown::Component
- Extended by:
- Forwardable
- Includes:
- Streamlined
- Defined in:
- bridgetown-core/lib/bridgetown-core/component.rb
Class Attribute Summary collapse
-
.source_location ⇒ Object
Returns the value of attribute source_location.
Instance Attribute Summary collapse
-
#site ⇒ Bridgetown::Site
readonly
-
#view_context ⇒ Bridgetown::RubyTemplateView, Bridgetown::Component
readonly
Class Method Summary collapse
-
.component_template_content ⇒ String
Read the template file.
-
.component_template_path ⇒ String
Find the first matching template path based on source location and extension.
-
.component_template_path_candidates ⇒ Array<String>
Provide a list of potential sidebar template paths (minus extensions).
-
.inherited(child) ⇒ Object
-
.path_for_errors ⇒ Object
-
.renderer_for_ext(ext) ⇒ Object
Return the appropriate template renderer for a given extension.
-
.supported_template_extensions ⇒ Array<String>
A list of extensions supported by the renderer TODO: make this extensible.
Instance Method Summary collapse
-
#_renderer ⇒ Object
-
#before_render ⇒ Object
Subclasses can override this method to perform tasks before a render.
-
#call ⇒ Object
Not used by default, but subclasses may utilize it (such as callable view objects).
-
#content ⇒ String
If a content block was originally passed into via
render, capture its output. -
#helpers ⇒ Object
-
#method_missing(method) ⇒ Object
-
#render(item = nil, **options, &block) ⇒ String
Provide a render helper for evaluation within the component context.
-
#render? ⇒ Boolean
Subclasses can override this method to determine if the component should be rendered based on initialized data or other logic.
-
#render_in(view_context, &block) ⇒ Object
This is where the magic happens.
-
#respond_to_missing?(method, include_private = false) ⇒ Boolean
-
#slot(name, input = nil, replace: false, &block) ⇒ void
Define a new component slot.
-
#slots ⇒ Array<Bridgetown::Slot>
-
#slotted(name, default_input = nil, &default_block) ⇒ String
Render out a component slot.
-
#slotted?(name) ⇒ Boolean
Check if a component slot has been defined.
-
#template ⇒ Object
Subclasses can override this method to return a string from their own template handling.
Methods included from Streamlined
Methods included from ERBCapture
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method) ⇒ Object
255 256 257 258 259 260 261 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 255 def method_missing(method, ...) if helpers.respond_to?(method.to_sym) helpers.send(method.to_sym, ...) else super end end |
Class Attribute Details
.source_location ⇒ Object
Returns the value of attribute source_location.
18 19 20 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 18 def source_location @source_location end |
Instance Attribute Details
#site ⇒ Bridgetown::Site (readonly)
12 13 14 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 12 def site @site end |
#view_context ⇒ Bridgetown::RubyTemplateView, Bridgetown::Component (readonly)
15 16 17 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 15 def view_context @view_context end |
Class Method Details
.component_template_content ⇒ String
Read the template file.
98 99 100 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 98 def component_template_content @_tmpl_content ||= File.read(component_template_path) end |
.component_template_path ⇒ String
Find the first matching template path based on source location and extension.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 79 def component_template_path @_tmpl_path ||= component_template_path_candidates.map do |candidate| found_path = supported_template_extensions.map do |ext| path = "#{candidate}.#{ext}" break path if File.exist?("#{candidate}.#{ext}") end break found_path unless Array(found_path).first.nil? end if Array(@_tmpl_path).first.nil? raise "#{name}: no matching template could be found in #{File.dirname(source_location)}" end @_tmpl_path end |
.component_template_path_candidates ⇒ Array<String>
Provide a list of potential sidebar template paths (minus extensions).
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 58 def component_template_path_candidates @_tmpl_path_candidates ||= begin dirname = File.dirname(source_location) basename = File.basename(source_location, ".*") stripped_path = File.join(dirname, basename) paths = [] # same-file inner classes get folders of their own. `Shared::Navbar` inside `shared.rb` # will get `shared/navbar.erb` if (parent_name = nested_parent&.name&.underscore) && (dirname.split("/").last != parent_name) paths << File.join(dirname, parent_name, nested_name.underscore) end paths + [stripped_path, "#{stripped_path}.html"] # that last one is a compatibility nod end end |
.inherited(child) ⇒ Object
20 21 22 23 24 25 26 27 28 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 20 def inherited(child) # Code cribbed from ViewComponent by GitHub: # Derive the source location of the component Ruby file from the call stack child.source_location = caller_locations(1, 10).reject do |l| l.label == "inherited" end[0].absolute_path super end |
.path_for_errors ⇒ Object
110 111 112 113 114 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 110 def path_for_errors File.basename(component_template_path) rescue RuntimeError, TypeError source_location end |
.renderer_for_ext(ext) ⇒ Object
Return the appropriate template renderer for a given extension. TODO: make this extensible
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 34 def renderer_for_ext(ext, &) @_tmpl ||= case ext.to_s when "erb" Tilt::ErubiTemplate.new(component_template_path, outvar: "@_erbout", bufval: "Bridgetown::OutputBuffer.new", engine_class: Bridgetown::ERBEngine, &) when "serb" Tilt::SerbeaTemplate.new(component_template_path, &) when "slim" # requires bridgetown-slim Slim::Template.new(component_template_path, &) when "haml" # requires bridgetown-haml Tilt::HamlTemplate.new(component_template_path, &) else raise NameError end rescue NameError, LoadError raise "No component rendering engine could be found for .#{ext} templates" end |
.supported_template_extensions ⇒ Array<String>
A list of extensions supported by the renderer TODO: make this extensible
106 107 108 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 106 def supported_template_extensions %w(erb serb slim haml) end |
Instance Method Details
#_renderer ⇒ Object
242 243 244 245 246 247 248 249 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 242 def _renderer @_renderer ||= begin ext = File.extname(self.class.component_template_path).delete_prefix(".") self.class.renderer_for_ext(ext) { self.class.component_template_content }.tap do |rn| self.class.include(rn.is_a?(Tilt::SerbeaTemplate) ? Serbea::Helpers : ERBCapture) end end end |
#before_render ⇒ Object
Subclasses can override this method to perform tasks before a render.
234 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 234 def before_render; end |
#call ⇒ Object
Not used by default, but subclasses may utilize it (such as callable view objects)
229 230 231 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 229 def call nil end |
#content ⇒ String
If a content block was originally passed into via render, capture its output.
120 121 122 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 120 def content @_content ||= (view_context.capture(self, &@_content_block) if @_content_block) end |
#helpers ⇒ Object
251 252 253 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 251 def helpers @helpers ||= Bridgetown::RubyTemplateView::Helpers.new(self, view_context&.site) end |
#render(item = nil, **options, &block) ⇒ String
Provide a render helper for evaluation within the component context.
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 179 def render(item = nil, **, &block) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity return @_rbout if !block && .empty? && item.nil? # Defer to Streamline's rendering logic in this case return super if item.is_a?(Proc) || (block && item.nil?) if item.respond_to?(:render_in) result = "" capture do # this ensures no leaky interactions between BT<=>VC blocks result = item.render_in(self, &block) end result&.html_safe else partial(item, **, &block)&.html_safe end end |
#render? ⇒ Boolean
Subclasses can override this method to determine if the component should be rendered based on initialized data or other logic.
238 239 240 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 238 def render? true end |
#render_in(view_context, &block) ⇒ Object
This is where the magic happens. Render the component within a view context.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 199 def render_in(view_context, &block) @view_context = view_context @_content_block = block if render? if helpers.site.config.fast_refresh signal = helpers.site.tmp_cache["comp-signal:#{self.class.source_location}"] ||= Signalize.signal(1) # subscribe so resources are attached to this component within effect signal.value end before_render template else "" end rescue StandardError => e Bridgetown.logger.error "Component error:", "#{self.class} encountered an error while " \ "rendering `#{self.class.path_for_errors}'" raise e end |
#respond_to_missing?(method, include_private = false) ⇒ Boolean
263 264 265 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 263 def respond_to_missing?(method, include_private = false) helpers.respond_to?(method.to_sym, include_private) || super end |
#slot(name, input = nil, replace: false, &block) ⇒ void
This method returns an undefined value.
Define a new component slot
135 136 137 138 139 140 141 142 143 144 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 135 def slot(name, input = nil, replace: false, &block) content = block.nil? ? input.to_s : view_context.capture(&block) name = name.to_s slots.reject! { _1.name == name } if replace slots << Slot.new(name:, content:, context: self, transform: false) nil end |
#slots ⇒ Array<Bridgetown::Slot>
125 126 127 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 125 def slots @slots ||= [] end |
#slotted(name, default_input = nil, &default_block) ⇒ String
Render out a component slot
151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 151 def slotted(name, default_input = nil, &default_block) content # ensure content block is processed name = name.to_s filtered_slots = slots.select do |slot| slot.name == name end return filtered_slots.map(&:content).join.html_safe if filtered_slots.length.positive? default_block.nil? ? default_input.to_s : capture(&default_block) end |
#slotted?(name) ⇒ Boolean
Check if a component slot has been defined
167 168 169 170 171 172 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 167 def slotted?(name) name = name.to_s slots.any? do |slot| slot.name == name end end |
#template ⇒ Object
Subclasses can override this method to return a string from their own template handling.
224 225 226 |
# File 'bridgetown-core/lib/bridgetown-core/component.rb', line 224 def template (method(:call).arity.zero? ? call : nil) || _renderer.render(self) end |