Elements

An element is mostly a set of contents that belong together, visually and contextually.

Imagine you bundle a headline, a text block and a picture – that is a typical article element.

The element’s contents are also called essences and belong to certain essence types. Each essence type represents a data type that can be stored in a database.

Each element has two related view partials. One which renders the essences on your website, the other which renders form fields in the admin frontend for the editor. These partials can be generated automatically.

1 Defining elements

Elements get defined in the config/alchemy/elements.yml file.

If this file does not exist yet, use the following scaffold generator to do that now:


rails g alchemy:scaffold

The generator also creates all the other basic folders and files for setting up your website with Alchemy.

The element definitions are being cached. Please restart the server after editing the elements.yml.

1.1 Example of an element definition


# config/alchemy/elements.yml
- name: article
    unique: true
    contents:
    - name: image
      type: EssencePicture
    - name: headline
      type: EssenceText
      take_me_for_preview: true
    - name: copy
      type: EssenceRichtext

The element in this example is named article and can be placed only once per page. It has three contents of the following types: EssencePicture, EssenceText and EssenceRichtext. The “headline” content is used for the preview text in the element’s title bar in the admin frontend.

1.2 Element settings

The following settings can be used to define elements in the elements.yml.

  • name String (required)
    A lowercased unique name of the element. Separated words needs to be underscored. The name is used in the page_layouts.yml file to define on which pages the element can be used. It is also part of the app/views/alchemy/elements view partials file names. The name is translatable for the user in the admin frontend.
  • unique Boolean
    (Default: false) Passing true means this element can be placed only once on a page.
  • hint String
    A hint for the user in the admin frontend that describes what the element is used for. The hint is translatable if you provide an I18n translation key instead of a complete sentence.
  • available_contents Array
    A collection of contents that can be added to the element dynamically by the user in the admin frontend.
  • taggable Boolean
    Enables the element to be taggable by the user in the admin frontend.
  • picture_gallery Boolean
    Enables a picture gallery editor for the element.
  • contents Array
    A collection of contents the element contains. A content has to have a name (unique per element) and a type. Take a look at the essences guide to get more informations about the available essence types.

In the following examples you will see how to use these settings. In the code examples of the partials we use the slim template engine instead of ERB to keep the markup short and easy to understand.

Alchemy provides a nice picture gallery editor. It allows to manage large picture galleries very easy by dragging the pictures around.

In order to use it, you just need to enable the setting in the elements.yml file.


- name: picture_gallery
  picture_gallery: true

After generating the elements view partials you will receive the view and the editor partial.

1.3.1 The editor partial for the element

This partial holds the code for rendering the gallery editor in the admin frontend.


# app/views/alchemy/elements/_picture_gallery_editor.html.slim
= element_editor_for(element) do |el|
  = render_picture_gallery_editor(element, max_images: nil, crop: true)

You can pass optional settings to the render_picture_gallery_editor helper:


max_images: 10
image_size: "346x246"
crop: true
fixed_ratio: true
format: 'png'

max_images option limits the amount of pictures a user can add to the gallery.
crop enables the image cropping feature for the user.

1.3.2 The view partial for the element

The view partial gets rendered on your website.


= element_view_for(element, :id => 'gallery') do |el|
  - element.contents.gallery_pictures.each do |image|
    = render_essence_view(image, :image_size => "346x246", :crop => true)

Alternatively, if you want to customize the way the gallery pictures are rendered, you can create the plain image tag by yourself and use the Alchemy::Picture url helper:


<%= image_tag show_alchemy_picture_url(image.essence.picture, :size => "102x73", :format => "png") %>

1.4 Element with addable contents

You are able to allow your users to add contents dynamically to the element.

You just need to define available contents in the elements.yml. If you also want to allow the users to delete these contents you can also define that.

1.4.1 Example of element definition with available contents

- name: article
  contents:
  - name: headline
    type: EssenceText
  available_contents:
  - name: text
    type: EssenceRichtext
    settings:
      deletable: true

Additionally you need to alter the editor partial of that element for rendering the dynamically created contents. You just need to iterate through them and use the following helper methods.

1.4.2 Example of editor partial using the available contents feature

- element.all_contents_by_name('text').each do |content|
  = label_and_remove_link(content)
  = render_essence_editor(content)
p = render_create_content_link(element, 'text', :label => _t('add_text'))

As shown in the example, we used the label_and_remove_link helper. It renders a label with the content’s name and a small button to delete it.

We also used the render_create_content_link helper. It renders a button to create certain contents instantly.

Now an Alchemy user can add the content text from the element as much as desired and is able to delete them again.

If you have more than one available content, you can use the render_new_content_link helper instead. It will provide a button that opens an overlay with a selectbox holding all available contents.

1.5 Element with tags

Elements are taggable. To enable it, add taggable: true to the element’s definition.


- name: article
  taggable: true
  contents:
    - name: image
      type: EssencePicture
    - name: headline
      type: EssenceText
      take_me_for_preview: true
    - name: copy
      type: EssenceRichtext

Tags are a collection on the element object. element.tag_list returns an array of tag names.


= element.tag_list.join(', ')

Alchemy uses the acts-as-taggable-on gem, so please refer to the github readme or the wiki for further informations.

1.6 Element with content validations

You can enable validations for your contents. They behave like the Rails model validations.

Supported validations are: presence, uniqueness, format

format needs to come with a regular expression or a predefined matcher string as its value. There are already predefined format matchers listed in the config/alchemy/config.yml file. It is also possible to add own format matchers there.

1.6.1 Example of format matchers

# config/alchemy/config.yml
format_matchers:
  email: !ruby/regexp '/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/'
  url:   !ruby/regexp '/\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix'
1.6.2 Example of an element definition with essence validations:

- name: person
  contents:
  - name: name
    type: EssenceText
    validate: [presence]
  - name: email
    type: EssenceText
    validate: [format: 'email']
  - name: homepage
    type: EssenceText
    validate: [format: !ruby/regexp '/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/']

The email content is being validated against the predefined ‘email’ matcher in the config.yml. The homepage content is matched against the given regexp.

1.6.3 Example of an element definition with chained validations.

- name: person
  contents:
  - name: name
    type: EssenceText
    validate: [presence, uniqueness, format: 'name']

The validations are evaluated in the order as they are defined in the list. At first the name content will be validated for presence, then for uniqueness and at least against its format.

2 Assign elements to page layouts

Before you can use elements on pages, you need to define on which page layouts your element can be placed. So open config/alchemy/page_layouts.yml in your text editor and put the name of your new element into the list of available elements for a specific page layout.


- name: standard
  elements: [article]
  autogenerate: [article]

You can now place the article element on each page with page layout `standard`. All future created pages with page layout `standard` will automatically create the article element for you.

3 Generating the partials

After typing the line below in your terminal, the rails generator will create the elements editor and view files.


  rails g alchemy:elements --skip

You can pass --template-engine or -e as an argument to use haml, slim or erb. The default template engine depends on your settings in your Rails host app.

The generator will create two files for each element in your app/views/alchemy/elements folder.

According to the first example, the article element, the generator will create the _article_view.html.erb and _article_editor.html.erb files.

  1. The element’s view file _article_view.html.erb gets rendered, when a user requests your webpage.
  2. The element’s editor file _article_editor.html.erb gets rendered, when you edit the page in the admin frontend.

The generator does not only create these files, it also generates the necessary code for you. Mostly you can take use of the that code and make it nifty by adding some CSS stylings.

4 Render elements on a page layout

Now that the above ‘article’ element example is associated with the ‘standard’ page layout, the element can be rendered on that layout app/views/alchemy/page_layouts/_standard.html.erb.


...

<!-- Main Page Content -->
<div class="row">

  <!-- Main article Content -->
  <div class="large-9 columns" role="content">
    <%= render_elements only: 'article' %>
  </div>

...

5 Customizing the view partial

The Alchemy element generator creates the basic html markup for you.

Pretty useful, but maybe not what you need, sometimes. No problem, feel free to customize it. It’s yours :).

This is the newer notation for rendering the element’s partial:


<%= element_view_for(element) do |el| %>
  <h3><%= el.render :headline %></h3>
  <div class="row">
    <div class="large-6 columns">
      <p>
        <%= el.render :image %>
      </p>
    </div>
    <div class="large-6 columns">
      <p>
        <%= el.render :text %>
      </p>
    </div>
  </div>
<% end %>

In some cases you want to use the older style. You can also or mix both. Take a look at this example, its the same html output as the above example:


<div class="article" id="<%= element_dom_id(element) %>"<%= element_preview_code(element) -%>>
  <h3><%= render_essence_view_by_name(element, 'headline') %></h3>
  <div class="row">
    <div class="large-6 columns">
      <p>
        <%= render_essence_view_by_name(element, 'image') %>
      </p>
    </div>
    <div class="large-6 columns">
      <p>
        <%= render_essence_view_by_name(element, 'text') %>
      </p>
    </div>
  </div>
</div>

The element_view_for helper wraps the inner html code into a div element by default. You can pass arguments to the helper to change its rendering behavior:

The second argument tag is used for the wrapping html tag. Passing false to it means no wrapping at all. Passing the name of any html element to it means the inner html gets wrapped within the given html tag instead of the default div.

You can pass additional arguments to add or change any html attributes of the wrapper.


<%= element_view_for(element, tag: 'li', class: 'red', id: 'my_unique_id') do |el| %>
  ...
<% end %>

If you want to learn more about the helper methods used in these partials, please have a look at the Documentation.

The view partial of an element is used for all elements of that kind. If you want to have different HTML for one element, define a new one and place it on a specific page layout. It is common to have lots of elements defined that are grouped on several page layouts.

6 Translations

Element and content names are passed through the I18n library. You can translate them in your config/locales language yml file.


de:
  alchemy:
    element_names:
      contact_form: Kontaktformular
      search: Suche
    content_names:
      headline: Überschrift

Content names can also be translated related to their element. This is useful for contents with the same name that should have different translations.


de:
  alchemy:
    element_names:
      contact_form: Kontaktformular
    content_names:
      color: Farbe
      contact_form:
        color: Button Farbe