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 06 · Rendering areas in templates

Area introspection, raw content and attributes

Getters most templates never need: the type, category and editor of a layout area, for components that adapt to how an author configured it; the raw content objects the typed getters are built on; and the accessors that read an area's attributes.

Who this is for. The developer writing a shared component that has to work with layouts it did not design. Afterwards you can branch on how an area is configured, tell a stored value from a default, and read attributes whose names you do not know in advance.

Branching on how an area is configured

Three getters describe an area rather than its content: getAreaType(name) returns the input type record, getAreaCategory(name) its category and getAreaEditor(name) its editor, both as strings. All three return null when the area is empty or the layout does not have it, and they read only the area list already in memory, so they cost nothing.

The category is one of eight, and only the text category has an editor:

CategoryEditors
texttext, textarea, number, email, richtext, select, multiselect, switch, math, date, datetime, code, color
image, file, widget, component, collection, record, recordsNone

The widget category is deprecated: a component's template reads records with the query helper $_query instead (Page and component layouts).

One template can render an area correctly whatever the author put in it, by calling the getter that matches the category. getAreaHtml() is built the same way inside the framework, one branch per category. Below, the text branch prints the shorthand, {$component->media}; the record branch calls getAreaHtml(), because on a record area the shorthand returns the record itself rather than its rendered template.

Template code
{$kind = $component->getAreaCategory('media')}
{if $kind == 'image'}
  {$component->getAreaImage('media')->getResized(1200, 675, 'fit')->getHtml()}
{elseif $kind == 'record'}
  {$component->getAreaHtml('media')}
{elseif $kind == 'text'}
  <div class="media-text">{$component->media}</div>
{/if}
{% set kind = component.getAreaCategory('media') %}
{% if kind == 'image' %}
  {{ component.getAreaImage('media').getResized(1200, 675, 'fit').getHtml() }}
{% elseif kind == 'record' %}
  {{ component.getAreaHtml('media') }}
{% elseif kind == 'text' %}
  <div class="media-text">{{ component.media }}</div>
{% endif %}

Raw content

Under every typed getter is a content object, the stored content of the area. getAreaContent(name) returns what is stored, getAreaDefaultContent(name) the area's default, and getAreaContentOrDefault(name) the stored content, or the default when nothing is stored or what is stored is empty. Every typed getter (getAreaText(), getAreaImage(), getAreaRecords() and the rest) starts by calling getAreaContentOrDefault(), so most templates never need these three. Two cases do.

Telling a stored value from the default

The typed getters fall back to the default without saying so. To know whether the author set the area or left it at its default, read getAreaContent(): in a template it returns a blank content object when nothing is stored.

Template code
{$stored = $component->getAreaContent('theme')}
{if $stored->isEmpty()}
  {* the author left the area at its default *}
{/if}
{% set stored = component.getAreaContent('theme') %}
{% if stored.isEmpty() %}
  {# the author left the area at its default #}
{% endif %}

Reading a reference's ids without loading it

The content of a filled record or records area stores the referenced id as text, the ids separated by commas for records. getText() on the content object returns that string without looking the records up, which is enough to compare ids.

getAreaContent() on a page

On a page, a blog post or any other versioned record, getAreaContent() is not passed through to the active revision. Call it on the revision: $page->getActiveRevision()->getAreaContent('theme') in Smarty, page.getActiveRevision().getAreaContent('theme') in Twig. getAreaDefaultContent() and getAreaContentOrDefault() work on the record directly.

Attribute accessors

An attribute is a sub-area attached to an area on the layout, such as an image's alt text or its crop mode. Attributes: settings, not content explains how they are set up.

When a template knows an attribute's name, it reads it like any area, by that name: getAreaText('imageAlt') for an alt text, which goes into an HTML attribute and so takes the stored string rather than the shorthand. The attribute accessors are for a template that does not know the names in advance, such as a library component whose designer may attach any attributes:

  • getAreaAttributesContent(name): the stored content of each attribute;
  • getAreaAttributesDefaultContent(name): each attribute's default;
  • getAreaAttributesContentOrDefault(name): the stored content, or the default.

Each returns an array of content objects keyed by attribute name, and an empty array when the area has no attributes or the layout does not have the area. Read a value with getText(). The accessors are cheap: one read of already loaded content per attribute.

Reading an image area's alt text and crop mode, with fallbacks when an attribute is absent:

Template code
{$image = $component->getAreaImage('image')}
{$attrs = $component->getAreaAttributesContentOrDefault('image')}
{$alt = isset($attrs['imageAlt']) ? $attrs['imageAlt']->getText() : ''}
{$crop = isset($attrs['imageCrop']) ? $attrs['imageCrop']->getText() : 'fit'}
{if !$image->isBlank()}
  {$image->getResized(800, 450, $crop)->getHtml(['alt' => $alt])}
{/if}
{% set image = component.getAreaImage('image') %}
{% set attrs = component.getAreaAttributesContentOrDefault('image') %}
{% set alt = attrs.imageAlt is defined ? attrs.imageAlt.getText() : '' %}
{% set crop = attrs.imageCrop is defined ? attrs.imageCrop.getText() : 'fit' %}
{% if not image.isBlank() %}
  {{ image.getResized(800, 450, crop).getHtml({'alt': alt}) }}
{% endif %}
Go deeper

Was this page helpful?

Your answer goes to the docs team.