Attention
This manual is no longer being maintained for TYPO3 versions 11.5 and above. The majority of the content has been migrated to the Extbase or Fluid sections in "TYPO3 Explained".
Template Creation by example¶
In this section we will show you some of the techniques you got to know in the course so far, in the interaction with our sample extension sjr_offers. We will focus on practical solutions for repeating problems. The directory structure of the extension is shown in Figure 8-2. We are using both layouts and partials, to avoid double code. Inside Scripts we put JavaScript code that we use for animations and for a Date picker in the frontend.

Figure 8-2: Folder structure of layouts, templates and partials inside the extension sjr_offers¶
The extension sjr_offers has an
OfferController
and an OrganizationController
.
Using the OfferController
, offers can be displayed as a list
using the method indexAction()
or as single view using the
method showAction()
method. Also offers can be created using
the method newAction()
and available offers can be edited using
the method editAction()
. The
OrganizationController
incorporates the same actions for
organizations, with exception of the creation of organizations. Within the
folder EXT:sjr_offers/Resources/Private/Templates
we
have created a folder for each controller, without the suffix
Controller in the name. Each action method has its own
HTML template. There is also no suffix Action allowed
in the name.
Setting up the HTML basic framework¶
The various templates have many common elements. First we define the basic framework by a common layout (see the section Creating A Consistent Look And Feel With Layouts earlier in this chapter) and store repeating code in partials (see the section "Moving Repeating Snippets To Partials" earlier in this chapter). The basic framework of our templates looks as follows:
<f:layout name="default" />
<f:section name="content">
<!-- ... -->
</f:section>
In most templates we are referencing the layout
default
, that should build the "frame" of our plugin output.
The actual template resides in a section with the name
content
. The layout definition is stored in the HTML file
EXT:sjr_offers/Resources/Private/Layouts/default.html
<div class="tx-sjroffers">
<f:render section="content" />
<f:flashMessages id="dialog" title="Notice!"/>
</div>
A section content
of the respective template is
rendered and after this a message to the frontend user is shown if
necessary. The complete content of the plugin is then "packed" in a
div
container. The message - a so called flash
message - will be created inside our sample extension in the
controller, e.g. at unauthorized access (see also the sections for edit and delete controller actions in chapter 7):
public function updateAction(\MyVendor\SjrOffers\Domain\Model\Offer $offer)
{
$administrator = $offer->getOrganization()->getAdministrator();
if ($this->accessControlService->isLoggedIn($administrator)) {
// ...
} else {
$this->flashMessages->add('Please log in.');
}
// ...
}
Store functions in ViewHelper¶
With this the base framework of our plugin output is ready. In the templates of our sample extension there still exists some repeating jobs, which can be stored in ViewHelper classes.
One requirement for the extension is, that the organizations can
edit their (and only their) offers in the frontend. We have to control the
access at different levels, so that not every website user can change the
data. We have discussed the different level of the access control already
in chapter 7. One of those levels are the templates. Elements for editing
the data, like forms, links and icons, should only displayed when an
authorized administrator of the organization is logged in as frontend user
(see figure 8-3). In chapter 7 we suggested the
IfAuthenticatedViewHelper
and the
AccessControlService
, that we had implemented for this
purpose.

Figure 8-3: Single view of an organization with its offers (left) and the same view with shown editing symbols (right)¶
Another repeating job is the formatting of numbers and date
intervals, For example how the date is displayed for the
Offerperiod (Angebotszeitraum) in Figure 8-3. An offer can
have a minimum and/or a maximum amount of attendees for example. If none
of this is given, nothing should be displayed. If only one of these values
is given the value should be prefixed with from respectively to. We store
these jobs in a NumericalRangeViewHelper
and call it in our
template like this:
<sjr:format.numericRange>{offer.ageRange}</sjr:format.numericRange>
Alternatively you can use the inline notation of Fluid (therefore see the box Inline Notation Versus Tag Based Notation earlier in this chapter):
{offer.ageRange->sjr:format.numericRange()}
The NumericRangeViewHelper
is implemented as follows:
namespace MyVendor\SjrOffers\ViewHelpers\Format;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
class NumericRangeViewHelper extends AbstractViewHelper
{
/**
* @param \MyVendor\SjrOffers\Domain\Model\NumericRangeInterface $range The range
* @return string Formatted range
*/
public function render(\MyVendor\SjrOffers\Domain\Model\NumericRangeInterface $range = NULL)
{
$output = '';
if ($range === NULL) {
$range = $this->renderChildren();
}
if ($range instanceof \MyVendor\SjrOffers\Domain\Model\NumericRangeInterface) {
$minimumValue = $range->getMinimumValue();
$maximumValue = $range->getMaximumValue();
if (empty($minimumValue) && !empty($maximumValue)) {
$output = 'bis ' . $maximumValue;
} elseif (!empty($minimumValue) && empty($maximumValue)) {
$output = 'ab ' . $minimumValue;
} else {
if ($minimumValue === $maximumValue) {
$output = $minimumValue;
} else {
$output = $minimumValue . ' - ' . $maximumValue;
}
}
}
return $output;
}
}
The method render() has the optional argument $range
.
This is important for the inline notation. When this argument is not set
(also NULL
), the code between the starting and ending tag is
processed ("normal" notation) by calling the method
renderChildren()
. Is the result an object that implements the
NumericRangeInterface then the described use cases are checked step by
step and the resulting string is returned. In a similar manner the
DateRangeViewHelper
was implemented.
Design a form¶
At the end we show you another sample for designing a form for editing the basic data of an organization. You find the associated template edit.html in the folder EXT:sjr_offers/Resources/Private/Templates/Organization/.
{namespace sjr=MyVendor\SjrOffers\ViewHelpers}
<f:layout name="default" />
<f:section name="content">
<sjr:security.ifAuthenticated person="{organization.administrator}">
<f:then>
<f:render partial="formErrors" arguments="{formName: 'organization'}" />
<f:form class="tx-sjroffers-form" method="post" action="update" name="organization"
object="{organization}">
<label for="name">Name</label>
<f:form.textbox property="name" size="46" /><br />
<label for="address">Address</label>
<f:form.textarea property="address" rows="6" cols="46" /><br />
<label for="telephoneNumber">Telephone</label>
<f:form.textbox property="telephoneNumber" size="46" /><br />
<label for="telefaxNumber">Telefax</label>
<f:form.textbox property="telefaxNumber" size="46" /><br />
<label for="emailAddress">E-Mail</label>
<f:form.textbox property="emailAddress" size="46" /><br />
<label for="url">Homepage</label>
<f:form.textbox property="url" size="46" /><br />
<label for="description">Description</label>
<f:form.textarea property="description" rows="8" cols="46" /><br />
<f:form.submit value="Save"/>
</f:form>
</f:then>
<f:else>
<f:render partial="accessError" />
</f:else>
</sjr:security.ifAuthenticated>
</f:section>
The form is enclosed in the tags of the
IfAuthenticatedViewHelper
. If the access is granted than the
form is displayed, otherwise the content of the partial
accessError
is displayed.
<div id="dialog" title="Notice!">
You are not authorized to execute this action.
Please first log in with your username and password.
</div>
With the declaration of object="{organization}"
the
proper form is bound to the assigned Organization
object in
the editAction()
.<remark>TODO: Rewrite sentence</remark> The
form consists of input fields that are created by Fluid with the
form.textbox
ViewHelper respectively the
form.textarea
ViewHelper. Each form field is bound to their
specific property of the Organization
object using
property="telefaxNumber"
. The attribute value of the concrete
object is inserted in the form fields during rendering of the page. When
submitting the form, the data is send as POST parameters to the method
updateAction()
.
When the entered data is not valid, the method editActon()
is called again
and an error message is displayed. We have stored the HTML code for the error
message in a partial formErrors
(see
EXT:sjr_offers/Resources/Private/Partials/formErrors.html
). In this
partial, the name of the form that relates to the error message is given as
formName
:
<f:form.errors for="formName">
<div id="dialog" title="{error.propertyName}">
<p>
<f:for each="{error.errors}" as="errorDetail">
{errorDetail.message}
</f:for>
</p>
</div>
</f:form.errors>