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 07 · The shorthand, and working with each input type

Text, select and switch areas in templates

What text, textarea and rich text areas store and how to escape each; reading one select key or many and mapping them onto CSS classes; and why a switch is a boolean through the shorthand but the string 1 through getAreaText().

Who this is for. Developers rendering text-category areas, and content modellers choosing which editor an author gets. After this page you can print each of them safely in both engines and test a switch without surprises.

The shorthand and the getters used here are explained on How the area shorthand resolves. Multiselect values and dates read through explicit getters are also on Text, multiselect values and dates.

Text, textarea and rich text

All three store text, and getAreaText() returns it as stored. Nothing an author enters is escaped on the way out, in either engine: where escaping matters, the template does it.

EditorStoresGive it to authors forPrint it with
TextOne line of textHeadings, labels, button textThe shorthand
TextareaSeveral lines of plain text; line breaks stay line breaks and are never turned into <br>A short plain summary, an addressgetAreaText(), escaped, with line breaks converted
Rich textHTML from the editorFormatted body copy with links and listsThe shorthand, unescaped: escaping would print the tags
Template code
<h2>{$component->heading}</h2>
<p class="lead">{nl2br($component->getAreaText('summary')|escape)}</p>
<div class="prose">{$component->body}</div>
<h2>{{ component.heading }}</h2>
<p class="lead">{{ component.getAreaText('summary')|e|nl2br }}</p>
<div class="prose">{{ component.body }}</div>

The textarea uses the explicit getter because it is escaped: escaping the shorthand would also escape its live-preview wrapper, which then shows as code in the preview. In Smarty nl2br() is written as a function, because the security policy allows PHP functions but not PHP modifiers.

Output also depends on the editor in two ways:

  • Every text editor: images from the file manager are kept in the stored text as placeholders, and output turns them back into image markup.
  • Rich text only: a link to another record, inserted through the editor, is rewritten on output to that record's current address. A plain text or textarea area prints a link exactly as typed.

Select and multiselect

A select offers a fixed list of options, each defined on the input type as key::Label. Authors see the label; the key is stored, and getAreaText() returns it.

A multiselect stores several keys, as a JSON list. Read them with getAreaValues(), which returns the keys as an array; the shorthand would give the stored text, not a list.

Map keys straight onto CSS classes, so the stylesheet does the branching rather than the template. The keys go into attributes, so they are read with the explicit getters:

Template code
{* select "size": sm, md or lg *}
<a class="button button--{$component->getAreaText('size')}" href="{$component->getAreaText('ctaUrl')}">{$component->linkName}</a>

{* multiselect "hideOn": any of mobile, tablet, desktop *}
<div class="block{foreach $component->getAreaValues('hideOn') as $device} hide-{$device}{/foreach}">
{# select "size": sm, md or lg #}
<a class="button button--{{ component.getAreaText('size') }}" href="{{ component.getAreaText('ctaUrl') }}">{{ component.linkName }}</a>

{# multiselect "hideOn": any of mobile, tablet, desktop #}
<div class="block{% for device in component.getAreaValues('hideOn') %} hide-{{ device }}{% endfor %}">

The pattern scales to a whole component with a shared include: hand it a list of select areas, whose keys become classes, and a list of switch areas, whose names become classes when they are on. A new option is then a new CSS rule, not a template change.

Because the key is what you style on, choose keys that make good class names, and never change a key once content exists. Changing a label is safe; changing a key leaves every stored value pointing at an option that no longer exists. When the page needs the label rather than the key, the area's input type holds the options: getAreaType('size')->getPossibleValues().

Switch

A switch stores the text 1 when on and 0 when off. The admin form always sends one or the other, so a switch that has been saved is never empty. An area that has never been saved, such as one added to the layout after the content was written, has no value at all, and getAreaText() returns null.

ReadOnOffNever saved
The shorthand, {$component->hideDate}truefalsefalse
getAreaText('hideDate')'1''0'null
Template code
{* the shorthand: a real boolean *}
{if $component->hideDate}...{/if}

{* the getter: compare with the string *}
{if $component->getAreaText('hideDate') == '1'}...{/if}
{# the shorthand: a real boolean #}
{% if component.hideDate %}...{% endif %}

{# the getter: compare with the string #}
{% if component.getAreaText('hideDate') == '1' %}...{% endif %}

The string is where people get caught. Inside the template, '0' is false in both engines, so a bare test on the getter happens to work. Outside it, it does not: printed into a data- attribute for a script, '0' is a non-empty string, which JavaScript treats as true. Print an explicit value instead. The switch shorthand is safe inside the attribute, because a boolean is never wrapped for live preview:

Template code
<div class="gallery" data-autoplay="{if $component->autoplay}true{else}false{/if}">
<div class="gallery" data-autoplay="{{ component.autoplay ? 'true' : 'false' }}">

Through the shorthand, off and never saved are both false, so name a switch for the behaviour that is not the default: hideDate, not showDate. An area nobody has touched then gives the default.

Reading an area with the wrong getter

getAreaText() on an area that is not a text area does not fail. It returns a sentence, Error: cannot get text content for area of type followed by the area's type and category, and that sentence then appears on the page. If it shows up, the template is using the text getter on an image, file, record or component area: use the shorthand or the matching getter instead.

Dates, numbers, colours and code have the next page; files, records and component areas the one after.

Go deeper

Was this page helpful?

Your answer goes to the docs team.