# 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::EssencesHelper 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: # # # # === Fallback to elements from global page: # # You can use the fallback option as an override for elements that are stored on another page. # So you can take elements from a global page and only if the user adds an element on current page the # local one gets rendered. # # 1. You have to pass the the name of the element the fallback is for as for key. # 2. You have to pass a page_layout name or {Alchemy::Page} from where the fallback elements is taken from as from key. # 3. You can pass the name of element to fallback with as with key. This is optional (the element name from the for key is taken as default). # # <%= render_elements(fallback: { # for: 'contact_teaser', # from: 'sidebar', # with: 'contact_teaser' # }) %> # # === 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: # #
# <%= render_elements finder: MyCustomNewsArchive.new %> #
# # @option options [Alchemy::Page|String] :from_page (@page) # The page the elements are rendered from. You can pass a page_layout String or a {Alchemy::Page} object. # @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 [Hash] :fallback # Define elements that are rendered from another page. # @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 = {}) options = { from_page: @page, render_format: 'html' }.update(options) if options[:sort_by] Alchemy::Deprecation.warn "options[:sort_by] has been removed without replacement. " \ "Please implement your own element sorting by passing a custom finder instance to options[:finder]." end if options[:from_cell] Alchemy::Deprecation.warn "options[:from_cell] has been removed without replacement. " \ "Please `render element.nested_elements` instead." end finder = options[:finder] || Alchemy::ElementsFinder.new(options) elements = finder.elements(page: options[:from_page]) buff = [] elements.each_with_index do |element, i| buff << render_element(element, options, i + 1) end buff.join(options[:separator]).html_safe 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 # contents: # - name: text # type: EssenceText # # 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.available.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(*args) if args.length == 4 element, _part, options, counter = *args Alchemy::Deprecation.warn "passing a `part` parameter as second argument to `render_element` has been removed without replacement. " \ "You can safely remove it." else element, options, counter = *args end 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 element, { element: element, counter: counter, options: options }.merge(options.delete(: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 # Returns a string for the id attribute of a html element for the given element def element_dom_id(element) return "" if element.nil? "#{element.name}_#{element.id}".html_safe end # Renders the HTML tag attributes required for preview mode. def element_preview_code(element) if respond_to?(:tag_options) tag_options(element_preview_code_attributes(element)) else # Rails 5.1 uses TagBuilder tag_builder.tag_options(element_preview_code_attributes(element)) end 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 = {}) if respond_to?(:tag_options) tag_options(element_tags_attributes(element, options)) else # Rails 5.1 uses TagBuilder tag_builder.tag_options(element_tags_attributes(element, options)) end 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 # Sort given elements by content. # @deprecated # @param [Array] elements - The elements you want to sort # @param [String] content_name - The name of the content you want to sort by # @param [Boolean] reverse - Reverse the sorted elements order # # @return [Array] def sort_elements_by_content(elements, content_name, reverse = false) Alchemy::Deprecation.warn "options[:sort_by] is deprecated. Please implement your own element sorting." sorted_elements = elements.sort_by do |element| content = element.content_by_name(content_name) content ? content.ingredient.to_s : '' end reverse ? sorted_elements.reverse : sorted_elements end end end