How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Nest a component inside another and keep heading levels valid

A component with a collection of its own can hold components; the heading level is the thing that breaks. Keep the current level in the template context: read it, print, raise it for children, restore it afterwards.

Context

Context. Nesting is a collection area on a component layout: a "columns" component holds a collection whose elements are cards. The mechanics are the same loop as on a page. What goes wrong is the outline: every card prints an <h2> because it does not know it sits under the section's <h2>. The current level lives in the shared template context, which every render in the page can read and write.

Steps

  1. In the page template, after the <h1>, set the level to 2.
  2. In every component that prints a heading: read the level, print at it, set level + 1 for the children, render the children, restore the level.
  3. In a component that prints no heading, pass through: render the children without touching the level.

Template code

Template code
{* page template, after the h1 *}
{$_templateContext->set('headingLevel', 2)}

{* components/columns.html *}
{$level = $_templateContext->get('headingLevel', 2)}
{if !empty($component->heading)}
  <h{$level}>{$component->heading}</h{$level}>
  {$_templateContext->set('headingLevel', $level + 1)}
{/if}
<div class="columns">
{foreach $component->getAreaCollectionElements('columns') as $element}
  <div class="col">{$element->getComponent()->getHtml()}</div>
{/foreach}
</div>
{$_templateContext->set('headingLevel', $level)}
{# page template, after the h1 #}
{% do _templateContext.set('headingLevel', 2) %}

{# components/columns.html #}
{% set level = _templateContext.get('headingLevel', 2) %}
{% if component.heading is not empty %}
  <h{{ level }}>{{ component.heading }}</h{{ level }}>
  {% do _templateContext.set('headingLevel', level + 1) %}
{% endif %}
<div class="columns">
{% for element in component.getAreaCollectionElements('columns') %}
  <div class="col">{{ element.getComponent().getHtml() }}</div>
{% endfor %}
</div>
{% do _templateContext.set('headingLevel', level) %}

Why this and not that

Not a headingLevel variable passed through getHtml(): it works one level down and stops, and a passed variable cannot override one the page already holds. Not a fixed level per component ("cards are always h3"): the same card sits at three depths on one site. Cap the nesting with supported layouts at three levels; beyond that the outline is right and the page is slow.

Go deeper

Was this page helpful?

Your answer goes to the docs team.