Skip to main content

Templating

Flare's content elements render Twig templates. A Flare Engine object is made availble as flare variable in those templates to interface with the list or reader in the current context.

tip

Use the bundled templates as your starting point. They reflect the current runtime API.

Quick Start

info

When upgrading to Flare v0.1 – Older Flare documentation referred to Twig helpers such as flare_form(...), flare_make_list(...), and direct access to flare.entries. These functions and accessors are no longer available.

  • flare is the engine injected by the content element controller
  • flare.createView creates the runtime view object for the current context
    {% set flare_view = flare.createView %}
  • Reader templates work with a ValidationView
  • List templates work with an InteractiveView
    Within an InteractiveView:
    • Create the list view:
      {% set flare_list = flare.createView %}
    • Create the Symfony Form views for filtering:
      {% set form = flare_list.form.createView %}
    • Access entries:
      {% for entry in flare_list.entries %}
      <h3>{{ entry.headline }}</h3>
      {% endfor %}
    • Display a paginator:
      {{ include('@Contao/flare/paginator.html.twig', { paginator: flare_list.paginator }) }}

Template Locations

Bundled Templates

The bundled templates live in:

vendor/heimrichhannot/contao-flare-bundle/contao/templates/

The main content element templates are:

vendor/heimrichhannot/contao-flare-bundle/contao/templates/content_element/flare_listview.html.twig
vendor/heimrichhannot/contao-flare-bundle/contao/templates/content_element/flare_reader.html.twig

Related partials live in:

vendor/heimrichhannot/contao-flare-bundle/contao/templates/flare/

Custom Templates

To override or extend the provided templates, create Twig files in a Twig template directory of your Contao installation. Assuming there is a .twig-root file in contao/templates/, a typical structure looks like this:

contao/templates/content_element/{flare_listview,flare_reader}/my_variant.html.twig

You can then select the variant via the content element's customTpl field.

List View Templating

The default list template creates an interactive view first and then renders the filter form, entries, and paginator from that view.

contao/templates/content_element/flare_listview/my_variant.html.twig
{% extends "@Contao/content_element/flare_listview.html.twig" %}

{% set flare_list = flare.createView %}

{% block filter %}
{% set form = flare_list.form.createView %}

{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit">{{ 'submit'|trans({}, 'flare_form') }}</button>
{{ form_end(form) }}
{% endblock %}

{% block list %}
{% for entry in flare_list.entries %}
{% set href = flare_list.to(entry.id) %}

<article>
<h3>
{% if href %}
<a href="{{ href }}">{{ entry.headline ?? entry.title ?? '' }}</a>
{% else %}
{{ entry.headline ?? entry.title ?? '' }}
{% endif %}
</h3>
</article>
{% else %}
<p>No entries found.</p>
{% endfor %}
{% endblock %}

{% block pagination %}
{{ include('@Contao/flare/paginator.html.twig', {
paginator: flare_list.paginator
}) }}
{% endblock %}

Available Variables in List Templates

The list content element injects these template variables:

  • flare: the Engine
  • content_model: the tl_content model of the current content element
  • headline: the normalized Contao headline data

After creating the view with flare.createView, you typically work with:

  • flare_list.form: the Symfony form object
  • flare_list.entries: the current result set
  • flare_list.models: the current result set as Contao models
  • flare_list.paginator: the paginator
  • flare_list.count: the total result count
  • flare_list.to(id): the reader URL for an entry, if configured
  • flare_list.model(id): the Contao\Model instance for a specific entry

Entries and Models

In list templates, flare_list.entries yields associative arrays for the current result set.

If you need access to the full model collection directly, use flare_list.models:

{% for model in flare_list.models %}
<h3>{{ model.headline ?? model.title ?? '' }}</h3>
{% endfor %}

If you need models only occasionally, you can still iterate over flare_list.entries and resolve a specific model from the current entry:

{% for entry in flare_list.entries %}
{% set model = flare_list.model(entry.id) %}
<h3>{{ model.headline ?? entry.headline ?? '' }}</h3>
{% endfor %}

Under the hood, flare_list.model(id) may resolve each model from the cache or from the database individually, while flare_list.models resolves all models at once. In any case, models are registered from the raw result set (i.e. flare_list.entries) and cached for later use, eliminating unnecessary database queries.

note
  • Use flare_list.entries when the raw result data is sufficient.
  • If you only need models occasionally throughout the loop, using flare_list.model(entry.id) is perfectly fine.
  • If you need models throughout the whole loop, flare_list.models is the better choice than resolving each model individually.

Customizing the Filter Form

The filter form is a normal Symfony form. Create the form view from flare_list.form and apply form themes as usual.

{# assuming {% set flare_list = flare.createView %} has been defined #}
{% set form = flare_list.form.createView %}

You can access individual form fields by their field name. That field name is also used for the submitted query parameter. For example, if a field is named search, you can access it as form.search:

{% set search_field = form.search %}
{{ form_row(search_field) }}
{% block filter %}
{% set flare_list = flare.createView %}
{% set form = flare_list.form.createView %}

{% form_theme form with [
'bootstrap_5_layout.html.twig',
'form/my_symfony_form_theme.html.twig',
] %}

{{ form_start(form) }}

{% set search_field = form.search %}

<div class="my-form-fields">
<div class="my-custom-search-field">
{{ form_row(search_field) }}
</div>
{{ form_rest(form) }}
</div>

{% block submit %}
<button type="submit">Apply filter</button>
{% endblock %}

{% block reset %}
<button type="reset" class="visually-hidden-focusable">Reset filter</button>
{% endblock %}

{{ form_end(form) }}
{% endblock %}
info

Because form themes are standard Symfony form themes, place them in templates/form/ relative to the project root, not in the Contao templates directory.

Reader Templating

The reader content element renders a single model. Its default template extends the same base template as the list view, but works with reader-specific variables.

contao/templates/content_element/flare_reader/my_variant.html.twig
{% extends "@Contao/content_element/flare_reader.html.twig" %}

{% block content %}
<article>
<h1>{{ model.headline ?? model.title ?? '' }}</h1>

{% if model.teaser %}
<div>
{{ model.teaser|raw }}
</div>
{% endif %}
</article>
{% endblock %}

Available Variables in Reader Templates

The reader content element injects these variables:

  • flare: the Engine
  • flare_reader: the pre-defined ValidationView created from flare
  • model: the resolved Contao\Model for the current reader item
  • content_model: the tl_content model of the current content element
  • headline: the normalized Contao headline data
  • comments: optional comments data when the comments integration attaches it

The reader template can also use flare_reader.to(id) if you need to generate reader links from within the validation view, i.e., linking to other entries of the same list.

note

Within a reader template, creating a separate view with flare.createView is typically not necessary. Instead of a flare_list variable, you can use flare_reader directly.

In a ValidationView, no Symfony form is available for filtering, meaning you cannot use flare_reader.form or similar.

Block Structure

These are the main blocks available for extension.

Base Template

@Contao/flare/_flare_base.html.twig defines:

  • wrapper
  • headline
  • content
  • footer

List Template

@Contao/content_element/flare_listview.html.twig adds:

  • content_start
  • filter
  • list
  • pagination
  • content_end

Reader Template

@Contao/content_element/flare_reader.html.twig mainly customizes:

  • content
  • footer

Paginator Templates

Flare ships several paginator templates.

Default paginator

{{ include('@Contao/flare/paginator.html.twig', {
paginator: flare_list.paginator
}) }}

The default paginator supports use_default_styles:

{{ include('@Contao/flare/paginator.html.twig', {
paginator: flare_list.paginator,
use_default_styles: true
}) }}

Bootstrap 5 variants

Flare also ships:

  • @Contao/flare/paginator/bs5.html.twig
  • @Contao/flare/paginator/bs5_extended.html.twig

These are useful if you want predefined Bootstrap-compatible paginator markup.

Modding the Flare Engine

Influencing list filtering or filter form rendering is possible by adding mods to the flare engine before creating the view.

{% do flare.addMod(mod_type, mod_options) %}

For example, add filters dynamically or change the paginator url query parameter.

{%
set modded_view = flare
.addMod('equation', { ... })
.addMod('query_param', { ... })
.createView()
%}

If you want to create multiple views consecutively with different mods, use flare.clearMods() to clear the current mod stack and start fresh.

See the Developers / Engine Mods page to learn more about mods and how to create custom ones.

Bundled Mods

Currently, only the following few mods are built-in.

Filter: Simple equation

Useful for showing multiple lists of different archives or categories on the same page. Just use one list view content element and display the different lists with different mods.

  • Mod type: equation
  • Mod options:
    • operand1 (string): the database column name to filter on,
    • operator (string): the operator, e.g. =, >=, LIKE, etc.,
    • operand2: the second operand, e.g., a string to match, a number to compare, an ID, etc.

Form: Query parameter

To add individual paginators to multiple lists created in one template, use this mod to change the query parameter used for pagination.

  • Mod type: query_param
  • Mod options:
    • param (string): the query parameter name.

Examples

{# @var \HeimrichHannot\FlareBundle\Engine\Engine flare #}
{% extends '@Contao/content_element/flare_listview.html.twig' %}

{# @var \HeimrichHannot\FlareBundle\Engine\View\InteractiveView flare_list #}
{% set flare_list = flare.createView %}
{% set form = flare_list.form.createView %}

{% macro render_list(list_view, headline) %}
<article class="flare-listview content-flare-listview block">
<h2>{{ headline }}</h2>
{% for entry in list_view.entries %}
{# Render the entry here #}
{% endfor %}
<footer>
{{ include('@Contao/flare/paginator.html.twig', { paginator: list_view.paginator }) }}
</footer>
</article>
{% endmacro %}

{% block list %}

{% set list_view = flare
.addMod('equation', { operand1: 'medium', operator: '=', operand2: 'video' })
.addMod('query_param', { param: 'video_page' })
.createView()
%}
{{ _self.render_list(list_view, 'Videos') }}

{% set list_view = flare
.clearMods()
.addMod('equation', { operand1: 'medium', operator: '=', operand2: 'podcast' })
.addMod('query_param', { param: 'podcast_page' })
.createView()
%}
{{ _self.render_list(list_view, 'Podcasts') }}

{% endblock %}

Debugging

We are working on improving ways to explore and document the available options per mod.

tip

Providing an empty or malconfigured config dictionary will typically raise an exception informing you on required and available option keys.

{% do flare.addMod('equation', { what: 'ever' }).createView %}

Resulting in an exception like this:

An exception has been thrown during the rendering of a template
("The option "what" does not exist. Defined options are: "name", "operand1", "operand2", "operator".")

Twig Helpers

Flare currently registers these Twig functions:

  • flare_content(model) for rendering the content elements belonging to the model.
  • flare_enclosure(model|entry_row, string field = "enclosure") for retreiving file enclosure data as you would with
    \Contao\Controller::addEnclosuresToTemplate(...)
  • flare_enclosure_files(model|enclosure_array) (deprecated, will be removed in v0.2): retreives a collection of Contao\FilesModel instances for the given model or an imediate array of enclosure data.
  • flare_schema_org(?model = null) (uses model from context if not provided): prints the JSON-LD schema markup for the given model in a reader context. The data can be influenced using the ReaderSchemaOrgEvent.

Comments Integration

Comments are not universally available. They are attached by Flare's comments integration when the current list and reader setup supports them.

The default reader template renders comments in its footer block if comments data is present:

{% block footer %}
{% if comments|default(false) %}
{{ include('@Contao/flare/comments/basic.html.twig', comments) }}
{% endif %}
{% endblock %}