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
Nesting components, and what depth costs
Components, collections and global componentsPage 17 of 45Updated
A component can hold its own collection, so blocks nest to any depth. The nested loop in both engines, the two costs that grow with depth, how to count what a page renders, and the ways to flatten a tree that has grown too deep.
Who this is for. Content modellers designing section, row and column blocks, and developers asked why one page is slow. After this page you can build one level of nesting cleanly, measure a deep tree, and redesign it when it costs too much.
A component that holds a collection
A component layout can have a component or collection area of its own. A Columns block with a columns collection whose elements are Column blocks, each with an items collection of cards, is three levels. The platform puts no limit on depth, and with an empty supported-layouts list a block can even be placed inside another of its own kind. The nested template is the same loop, read from component instead of page:
{% set columns = component.getAreaCollectionElements('columns') %}
<div class="columns columns--{{ columns|length }}">
{% for element in columns %}
<div class="columns__item">
{{ element.getComponent().getHtml({'loadPriority': loadPriority|default('lazy')}) }}
</div>
{% endfor %}
</div>
The parent passes its own loadPriority on, so a column inside an eager section loads eagerly too. Printed with the shorthand instead, {$component->columns} in Smarty or {{ component.columns }} in Twig, each child would receive parent, this component, and its index.
The two costs of depth
Render time. Every component is a separate template render, with its own content to load. Eight sections of four columns of one card each is 8 + 32 + 32 = 72 renders, and every card that reads a record area adds a lookup. The first visitor after a cache clear pays for all of them.
Author confusion. Every level is a component inside a component in the editing form. Past two levels, authors lose track of where they are, and move or delete the wrong block.
Keep to two levels as the rule, and allow a third only with a reason. Make the rule hold with supported layouts: a column's collection that accepts only cards cannot receive another columns block.
Measuring a deep tree
Count before redesigning. On a development environment, a counter in _templateContext, which every render of the request shares, tells you how many components a page renders. Add the first line to each component template and print the result at the foot of the page template:
Template code
{* first line of every component template *}
{$_templateContext->set('componentRenders', $_templateContext->get('componentRenders', 0) + 1)}
{* at the foot of the page template *}
<!-- components rendered: {$_templateContext->get('componentRenders', 0)} -->
{# first line of every component template #}
{% do _templateContext.set('componentRenders', _templateContext.get('componentRenders', 0) + 1) %}
{# at the foot of the page template #}
<!-- components rendered: {{ _templateContext.get('componentRenders', 0) }} -->
Then time the page with the page cache out of the way, and again on a copy of the page with the deepest collection emptied: the difference is what the tree costs. How to measure a page without the cache, and what the numbers mean, is in Measure first and The cost of nesting.
Flattening
Replace a structural level with a setting. Instead of section → row → column → card, give the section a columns select (two, three, four) as an attribute and one collection of cards; the CSS grid does the rest.
Replace a list of near-identical blocks with a records area when the cards only show other records: one area, one query, one template loop.
Move shared data up. Read anything every child needs (a category name, a shared image) once in the parent and pass it down through getHtml(), rather than in every child.
When a page has more than three levels or more than about fifty components, question the design rather than optimise it: the page is trying to be an application, which is better built as one component with one query behind it.