AlchemyCMS/alchemy_cms

View on GitHub
app/helpers/alchemy/elements_block_helper.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
# frozen_string_literal: true

module Alchemy
  # Provides a collection of block-level helpers, allowing for a much more
  # concise way of writing element view/editor partials.
  #
  module ElementsBlockHelper
    # Base class for our block-level helpers.
    #
    class BlockHelper
      attr_reader :helpers
      attr_reader :opts

      def initialize(helpers, opts = {})
        @helpers = helpers
        @opts = opts
      end

      def element
        opts[:element]
      end
    end

    # Block-level helper class for element views.
    #
    class ElementViewHelper < BlockHelper
      # Renders one of the element's ingredients.
      #
      # If the element uses +ingredients+ it renders the ingredient record.
      #
      def render(name, options = {}, html_options = {})
        renderable = element.ingredient_by_role(name)
        return if renderable.nil?

        helpers.render(
          renderable.as_view_component(
            options: options,
            html_options: html_options
          )
        )
      end

      # Returns the value of one of the element's ingredients.
      #
      def value(name)
        element.value_for(name)
      end

      # Returns true if the given ingredient has a value.
      #
      def has?(name)
        element.has_value_for?(name)
      end

      # Return's the ingredient record by given role.
      #
      def ingredient_by_role(role)
        element.ingredient_by_role(role)
      end
    end

    # Block-level helper for element views. Constructs a DOM element wrapping
    # your content element and provides a block helper object you can use for
    # concise access to Alchemy's various helpers.
    #
    # === Example:
    #
    #   <%= element_view_for(element) do |el| %>
    #     <%= el.render :title %>
    #     <%= el.render :body %>
    #     <%= link_to "Go!", el.ingredient(:target_url) %>
    #   <% end %>
    #
    # You can override the tag, ID and class used for the generated DOM
    # element:
    #
    #   <%= element_view_for(element, tag: 'span', id: 'my_id', class: 'thing') do |el| %>
    #      <%- ... %>
    #   <% end %>
    #
    # If you don't want your view to be wrapped into an extra element, simply set
    # `tag` to `false`:
    #
    #   <%= element_view_for(element, tag: false) do |el| %>
    #      <%- ... %>
    #   <% end %>
    #
    # @param [Alchemy::Element] element
    #   The element to display.
    # @param [Hash] options
    #   Additional options.
    #
    # @option options :tag (:div)
    #   The HTML tag to be used for the wrapping element.
    # @option options :id (the element's dom_id)
    #   The wrapper tag's DOM ID.
    # @option options :class (the element's name)
    #   The wrapper tag's DOM class.
    # @option options :tags_formatter
    #   A lambda used for formatting the element's tags (see Alchemy::ElementsHelper::element_tags_attributes). Set to +false+ to not include tags in the wrapper element.
    #
    def element_view_for(element, options = {})
      options = {
        tag: :div,
        id: element.dom_id,
        class: element.name,
        tags_formatter: ->(tags) { tags.join(" ") }
      }.merge(options)

      # capture inner template block
      output = capture do
        yield ElementViewHelper.new(self, element: element) if block_given?
      end

      # wrap output in a useful DOM element
      if (tag = options.delete(:tag))
        # add preview attributes
        options.merge!(element_preview_code_attributes(element))

        # add tags
        if (tags_formatter = options.delete(:tags_formatter))
          options.merge!(element_tags_attributes(element, formatter: tags_formatter))
        end

        output = content_tag(tag, output, options)
      end

      # that's it!
      output
    end
  end
end