Guide 04 · Developer docs

Input types, layouts, components and images

The platform's central abstraction, in one line: an input type defines what an editor can enter, a layout area binds one input type to a named slot, a layout groups areas and points at a template, a record or component holds the content for each area, and the template reads it back. Read this book straight after the template languages.

L1–L3
Unit 05 · Components, collections and global components

The collection loop and rendering a component

The one loop every theme writes, in both engines, covering local and global components alike; what getHtml() hands the component's template; how to pass a value down to it; and why a variable set in the page template does not reach it.

Who this is for. Theme developers rendering collections and component areas. After this page you have one authoritative loop to copy, and you know exactly which variables a component's template can see.

Without a loop

When the page needs no control over each block, print the area with the shorthand. Every published element renders in order, each through its own layout's template:

Template code
<main class="sections">
  {$page->pageSections}
</main>
<main class="sections">
  {{ page.pageSections }}
</main>

Rendered this way, each component's template receives index, its position counting from 1, and, when the collection belongs to another component, parent, that component.

The loop

To wrap each block, count them, or pass something down, loop over the elements. On the site, an element's getComponent() returns its local component or, for a reference, the global component, and both render with getHtml(), so one call covers both kinds:

Template code templates/pages/pages/landing.html
{$elements = $page->getAreaCollectionElements('pageSections')}
<main class="sections sections--{count($elements)}">
{foreach $elements as $element}
  {$element->getComponent()->getHtml([
    'index'        => $element@iteration,
    'loadPriority' => ($element@iteration <= 2) ? 'eager' : 'lazy'
  ])}
{/foreach}
</main>
{% set elements = page.getAreaCollectionElements('pageSections') %}
<main class="sections sections--{{ elements|length }}">
{% for element in elements %}
  {{ element.getComponent().getHtml({
    'index': loop.index,
    'loadPriority': loop.index <= 2 ? 'eager' : 'lazy'
  }) }}
{% endfor %}
</main>

getAreaCollectionElements() returns only published elements, in order, and an empty list when there are none, so the loop and the count are always safe. Older themes branch on isGlobal() and call getGlobalComponent() or getComponent(); on the site that does the same as the loop above, which can replace it. Use this loop, rather than a variant, everywhere a collection needs one.

What getHtml() gives the component's template

getHtml() renders the component's layout template with three sets of variables, merged in this order of priority:

  1. The page's variables: everything the page template received from its controller (page, record and the rest) and from the platform (_currentSite, _globalComponents, _templateContext and the others);
  2. what you pass to getHtml();
  3. component, the component itself.

A higher set wins on a name clash. So a variable you pass cannot override one the page already has: passing page changes nothing. And never pass component: it would win over, and replace, the component being rendered. Pass new names only, such as loadPriority, headingLevel or isFirst, and give each a default in the component's template, which may also be rendered without it:

Template code templates/components/hero.html
{$priority = $loadPriority|default:'lazy'}
{$image = $component->getAreaImage('image')}
{if !$image->isBlank()}
  {$image->getResized(1600, 600, 'fit')->getHtml(['loading' => $priority])}
{/if}
<h2>{$component->heading}</h2>
{% set priority = loadPriority|default('lazy') %}
{% set image = component.getAreaImage('image') %}
{% if not image.isBlank() %}
  {{ image.getResized(1600, 600, 'fit').getHtml({'loading': priority}) }}
{% endif %}
<h2>{{ component.heading }}</h2>

What does not reach the component

A component is a separate render. A variable assigned in the page template, or in a parent component's template, is not visible inside it unless you pass it. For a value every render should share, such as the current heading level as components nest, use _templateContext, the one object that every render of the request shares: set('headingLevel', 2) in one template, get('headingLevel', 2) in another. On a Twig site, every {% set %} also writes its value into _templateContext, so choose distinctive variable names. The full heading-level pattern is on Heading levels and load priority.

A component whose layout has no template is left blank; one whose template file is missing prints Template not found in Smarty, and fails the page in Twig.

Go deeper

Was this page helpful?

Your answer goes to the docs team.