# frozen_string_literal: true
module Alchemy
# This helpers are useful to render elements from pages.
#
# The most important helper for frontend developers is the {#render_elements} helper.
#
module ElementsHelper
include Alchemy::UrlHelper
include Alchemy::ElementsBlockHelper
# Renders elements from given page
#
# == Examples:
#
# === Render only certain elements:
#
#
# <%= render_elements only: ['header', 'claim'] %>
#
#
# <%= render_elements except: ['header', 'claim'] %>
#
#
# === Render elements from global page:
#
#
#
# === Custom elements finder:
#
# Having a custom element finder class:
#
# class MyCustomNewsArchive
# def elements(page:)
# news_page.elements.named('news').order(created_at: :desc)
# end
#
# private
#
# def news_page
# Alchemy::Page.where(page_layout: 'news-archive')
# end
# end
#
# In your view:
#
#
#
# @option options [Alchemy::Page] :from_page (@page)
# The page the elements are rendered from.
# @option options [Array|String] :only
# A list of element names only to be rendered.
# @option options [Array|String] :except
# A list of element names not to be rendered.
# @option options [Number] :count
# The amount of elements to be rendered (begins with first element found)
# @option options [Number] :offset
# The offset to begin loading elements from
# @option options [Boolean] :random (false)
# Randomize the output of elements
# @option options [Boolean] :reverse (false)
# Reverse the rendering order
# @option options [String] :separator
# A string that will be used to join the element partials.
# @option options [Class] :finder (Alchemy::ElementsFinder)
# A class instance that will return elements that get rendered.
# Use this for your custom element loading logic in views.
#
def render_elements(options = {}, &blk)
options = {
from_page: @page,
render_format: "html"
}.update(options)
finder = options[:finder] || Alchemy::ElementsFinder.new(options)
page_version = if @preview_mode
options[:from_page]&.draft_version
else
options[:from_page]&.public_version
end
elements = finder.elements(page_version: page_version)
default_rendering = ->(element, i) { render_element(element, options, i + 1) }
capture do
if blk
elements.map.with_index(&blk)
else
elements.map.with_index(&default_rendering)
end.join(options[:separator]).html_safe
end
end
# This helper renders a {Alchemy::Element} view partial.
#
# A element view partial is the html snippet presented to the website visitor.
#
# The partial is located in app/views/alchemy/elements.
#
# == View partial naming
#
# The partial has to be named after the name of the element as defined in the elements.yml file.
#
# === Example
#
# Given a headline element
#
# # elements.yml
# - name: headline
# ingredients:
# - role: text
# type: Text
#
# Then your element view partial has to be named like:
#
# app/views/alchemy/elements/_headline.html.{erb|haml|slim}
#
# === Element partials generator
#
# You can use this handy generator to let Alchemy generate the partials for you:
#
# $ rails generate alchemy:elements --skip
#
# == Usage
#
# <%= render_element(Alchemy::Element.published.named(:headline).first) %>
#
# @param [Alchemy::Element] element
# The element you want to render the view for
# @param [Hash] options
# Additional options
# @param [Number] counter
# a counter
#
# @note If the view partial is not found
# alchemy/elements/_view_not_found.html.erb gets rendered.
#
def render_element(element, options = {}, counter = 1)
if element.nil?
warning("Element is nil")
render "alchemy/elements/view_not_found", {name: "nil"}
return
end
element.store_page(@page)
render(
partial: options[:partial] || element.to_partial_path,
object: element,
locals: {
element: element,
counter: counter,
options: options.except(:locals, :partial)
}.merge(options[:locals] || {})
)
rescue ActionView::MissingTemplate => e
warning(%(
Element view partial not found for #{element.name}.\n
#{e}
))
render "alchemy/elements/view_not_found", name: element.name
end
# Renders the HTML tag attributes required for preview mode.
def element_preview_code(element)
tag_builder.tag_options(element_preview_code_attributes(element))
end
# Returns a hash containing the HTML tag attributes required for preview mode.
def element_preview_code_attributes(element)
return {} unless element.present? && @preview_mode && element.page == @page
{"data-alchemy-element" => element.id}
end
# Returns the element's tags information as a string. Parameters and options
# are equivalent to {#element_tags_attributes}.
#
# @see #element_tags_attributes
#
# @return [String]
# HTML tag attributes containing the element's tag information.
#
def element_tags(element, options = {})
tag_builder.tag_options(element_tags_attributes(element, options))
end
# Returns the element's tags information as an attribute hash.
#
# @param [Alchemy::Element] element The {Alchemy::Element} you want to render the tags from.
#
# @option options [Proc] :formatter
# ('lambda { |tags| tags.join(' ') }')
# Lambda converting array of tags to a string.
#
# @return [Hash]
# HTML tag attributes containing the element's tag information.
#
def element_tags_attributes(element, options = {})
options = {
formatter: lambda { |tags| tags.join(" ") }
}.merge(options)
return {} if !element.taggable? || element.tag_list.blank?
{"data-element-tags" => options[:formatter].call(element.tag_list)}
end
end
end