.. include:: /Includes.rst.txt
.. _template-creation-by-example:
============================
Template creation by example
============================
This section 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:: /Images/ExternalScreenshots/8-Fluid/figure-8-2.png
:align: center
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 a 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, except for the creation of organizations. Within the
folder :file:`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 :ref:`creating-a-consistent-look-and-feel-with-layouts` earlier in this chapter) and store
repeating code in partials (see the section ":ref:`moving-repeating-snippets-to-partials`" earlier in this chapter). The basic
framework of our templates looks as follows:
.. code-block:: html
:caption: EXT:sjr_offers/Resources/Private/Templates/SomeTemplate.html
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
:file:`EXT:sjr_offers/Resources/Private/Layouts/default.html`.
.. code-block:: html
:caption: EXT:sjr_offers/Resources/Private/Layouts/default.html
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 :ref:`chapter 7 `):
.. code-block:: php
:caption: sjr_offers/Classes/Controller/OfferController.php
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 exist 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 levels of access control already
in chapter 7. One of those levels is the templates. Elements for editing
the data, like forms, links, and icons, should only be displayed when an
authorized administrator of the organization is logged in as a frontend user
(see figure 8-3). In chapter 7, we suggested the
``IfAuthenticatedViewHelper`` and the
``AccessControlService``, that we had implemented for this
purpose.
.. figure:: /Images/ManualScreenshots/Frontend/8-Fluid/figure-8-3.png
:align: center
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:
.. code-block:: html
:caption: EXT:sjr_offers/Resources/Private/Layouts/default.html
{offer.ageRange}
Alternatively you can use the inline notation of Fluid (therefore
see the box
:ref:`Inline Notation Versus Tag Based Notation `
earlier in this chapter):
``{offer.ageRange->sjr:format.numericRange()}``
The `NumericRangeViewHelper` is implemented as follows:
.. code-block:: php
:caption: EXT:sjr_offers/Classes/ViewHelpers/Format/NumericRangeViewHelper.php
namespace MyVendor\SjrOffers\ViewHelpers\Format;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use MyVendor\SjrOffers\Domain\Model\NumericRangeInterface;
class NumericRangeViewHelper extends AbstractViewHelper
{
/**
* @param \NumericRangeInterface $range The range
* @return string Formatted range
*/
public function render(NumericRangeInterface $range = NULL)
{
$output = '';
if ($range === NULL) {
$range = $this->renderChildren();
}
if ($range instanceof 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
=============
In the end, we show you another sample for designing a form for
editing the basic data of an organization. You find the associated
template :file:`Edit.html` in the folder
:file:`EXT:sjr_offers/Resources/Private/Templates/Organization/`.
.. code-block:: html
:caption: EXT:sjr_offers/Resources/Private/Templates/Organization/Edit.html
{namespace sjr=MyVendor\SjrOffers\ViewHelpers}
The form is enclosed in the tags of the
``IfAuthenticatedViewHelper``. If access is granted, then the
form is displayed. Otherwise, the content of the partial
``AccessError`` is displayed.
.. code-block:: html
:caption: Example frontend output
You are not authorized to execute this action.
Please first log in with your username and password.
With the declaration of ``object="{organization}"`` the
proper form is bound to the assigned ``Organization`` object in
the ``editAction()``.TODO: Rewrite sentence 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 the page's rendering. When
submitting the form, the data is sent 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
:file:`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``:
.. code-block:: html
:caption: EXT:sjr_offers/Resources/Private/Partials/FormErrors.html
{errorDetail.message}
.. sidebar:: Localize error messages
The error messages of the default validators that are delivered
with Extbase are not localized in version 1.2 (TYPO3 4.4). You can translate the
messages yourself by replacing the before described partial
``formErrors`` with the following code:
.. code-block:: html
:caption: EXT:sjr_offers/Resources/Private/Partials/FormErrors.html
In the file
:file:`EXT:sjr_offers/Resources/Private/Language/locallang.xml`
you have to write for example:
.. code-block:: xml
:caption: EXT:sjr_offers/Resources/Private/Language/locallang.xml
This solution is only an agreement. The default localization of
the error messages are planned for a future version of
Extbase.
.. TODO: rework for current Extbase version