module ViewComponent::Slotable
Public Instance Methods
inherited(child)
click to toggle source
Calls superclass method
# File lib/view_component/slotable.rb, line 84 def inherited(child) # Clone slot configuration into child class # see #test_slots_pollution child.slots = self.slots.clone super end
slot(slot_name, **args, &block)
click to toggle source
Build a Slot
instance on a component, exposing it for use inside the component template.
slot: Name of Slot
, in symbol form **args: Arguments to be passed to Slot
initializer
For example: <%= render(SlotsComponent.new) do |component| %>
<% component.slot(:footer, class_names: "footer-class") do %> <p>This is my footer!</p> <% end %>
<% end %>
# File lib/view_component/slotable.rb, line 107 def slot(slot_name, **args, &block) # Raise ArgumentError if `slot` does not exist unless slots.keys.include?(slot_name) raise ArgumentError.new "Unknown slot '#{slot_name}' - expected one of '#{slots.keys}'" end slot = slots[slot_name] # The class name of the Slot, such as Header slot_class = self.class.const_get(slot[:class_name]) unless slot_class <= ViewComponent::Slot raise ArgumentError.new "#{slot[:class_name]} must inherit from ViewComponent::Slot" end # Instantiate Slot class, accommodating Slots that don't accept arguments slot_instance = args.present? ? slot_class.new(**args) : slot_class.new # Capture block and assign to slot_instance#content # rubocop:disable Rails/OutputSafety slot_instance.content = view_context.capture(&block).to_s.strip.html_safe if block_given? if slot[:collection] # Initialize instance variable as an empty array # if slot is a collection and has yet to be initialized unless instance_variable_defined?(slot[:instance_variable_name]) instance_variable_set(slot[:instance_variable_name], []) end # Append Slot instance to collection accessor Array instance_variable_get(slot[:instance_variable_name]) << slot_instance else # Assign the Slot instance to the slot accessor instance_variable_set(slot[:instance_variable_name], slot_instance) end # Return nil, as this method should not output anything to the view itself. nil end
with_slot(*slot_names, collection: false, class_name: nil)
click to toggle source
support initializing slots as:
:header, collection: true|false, class_name: "Header" # class name string, used to instantiate Slot
)
# File lib/view_component/slotable.rb, line 25 def with_slot(*slot_names, collection: false, class_name: nil) ActiveSupport::Deprecation.warn( "`with_slot` is deprecated and will be removed in ViewComponent v3.0.0.\n" \ "Use the new slots API (https://viewcomponent.org/guide/slots.html) instead." ) slot_names.each do |slot_name| # Ensure slot_name is not already declared if self.slots.key?(slot_name) raise ArgumentError.new("#{slot_name} slot declared multiple times") end # Ensure slot name is not :content if slot_name == :content raise ArgumentError.new ":content is a reserved slot name. Please use another name, such as ':body'" end # Set the name of the method used to access the Slot(s) accessor_name = if collection # If Slot is a collection, set the accessor # name to the pluralized form of the slot name # For example: :tab => :tabs ActiveSupport::Inflector.pluralize(slot_name) else slot_name end instance_variable_name = "@#{accessor_name}" # If the slot is a collection, define an accesor that defaults to an empty array if collection class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{accessor_name} content unless content_evaluated? # ensure content is loaded so slots will be defined #{instance_variable_name} ||= [] end RUBY else class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{accessor_name} content unless content_evaluated? # ensure content is loaded so slots will be defined #{instance_variable_name} if defined?(#{instance_variable_name}) end RUBY end # Default class_name to ViewComponent::Slot class_name = "ViewComponent::Slot" unless class_name.present? # Register the slot on the component self.slots[slot_name] = { class_name: class_name, instance_variable_name: instance_variable_name, collection: collection } end end