.. include:: /Includes.rst.txt .. index:: Fluid; Rendering Fluid; TemplateView Files; EXT:blog_example/Resources/Private/Templates =============================== Rendering the output with Fluid =============================== The TemplateView of Fluid now tries to load the corresponding HTML template. Since there is none explicitly specified by ``this->view->setTemplatePathAndFilename($template-PathAndFilename)`` Fluid searches at a place defined by conventions. All frontend templates can be found in :file:`EXT:blog_example/Resources/Private/Templates` by default. For example, there are the two subfolders *Blog* and *Post*. Since the call was made by the ``indexAction()`` of the ``BlogController``, Fluid searches in the folder *Blog* for a file named *Index* and - if not set up differently - the suffix *.html*. So every action method has its own template. In table 3.1 you can find some examples for this convention. *Table 3-1: Examples for the convention of template paths* +-----------+------------+------------+--------------------------------------------+ |Controller |Action |Format |Path and filename | +-----------+------------+------------+--------------------------------------------+ |Blog |index |unspecified |Resources/Private/Templates/Blog/Index.html | +-----------+------------+------------+--------------------------------------------+ |Blog |index |txt |Resources/Private/Templates/Blog/Index.txt | +-----------+------------+------------+--------------------------------------------+ |Blog |new |unspecified |Resources/Private/Templates/Blog/New.html | +-----------+------------+------------+--------------------------------------------+ |Post |unspecified |unspecified |Resources/Private/Templates/Post/Index.html | +-----------+------------+------------+--------------------------------------------+ In this case, the file *Index.html* will be loaded. Here you see an extract of the template file: .. code-block:: html :caption: Index.html :name: index-html

[Blog header]

[introduction]

[list of blogs] :

  1. {blog.title} ({f:translate(key: 'blog.numberOfPosts', arguments: '{numberOfPosts: \'{blog.posts -> f:count()}\'}')})

    {blog.description}

All the XML tags with namespace »f« stand out, like `` or ``. These tags are provided by Fluid and represent different functionalities. * `[…]` : modifies linebreaks (new lines) to `
` tags. * `` : creates a link tag that links to the :php:`editAction()` of the current controller. * `[…]` : iterates over the paginated Blog objects found in Blogs. * `[…]` : creates a link to the :php:`indexAction` method of the :php:`PostController` which is :php:`public function indexAction(Blog $blog, $tag = null)`. The blog stored in the variable `{blog}` is passed as `blog` parameter to the action. * `{f:translate(key: 'blog.numberOfPosts', arguments: '{numberOfPosts: \'{blog.posts -> f:count()}\'}')}` The translation key `blog.numberOfPosts` refers to the translation file:`Resources/Private/Language/locallang.xlf`. The number of `{blog.posts}` is counted using the `f:count()` viewhelper and passed as argument to the `f:translate()` viewhelper. This viewhelper inserts the argument into the translation text `%d posts`. `{blog.posts}` is the list of posts which belong to the current blog. The class `Blog` contains a getter method `getPosts()` which is automatically used to access the list of posts (:php:`\TYPO3\CMS\Extbase\Persistence\ObjectStorage`). In the variable `{blogs}` of the latter example all blogs are "included" and then split into "blogs per page" (paginatedItems) by the paginator. The details have to be set up in the controller, see the :ref:`documentation on pagination ` for a guide on how to achieve that. The curly brackets tell Fluid that it is a variable that has been "assigned" to the template. In our case this is done in the :php:`indexAction()` of the `BlogController`. With the attribute `each`, the `for` ViewHelper gets the `blog` objects over whom to iterate. The attribute ``as`` holds the name of the variable with which the `blog` object is available inside of `[...]`. Here it can be called with `{blog}`. .. note:: The string `"blog"` is *not* surrounded by brackets when assigned to the `as` attribute since the string is passed as a *name* for the variable and should not be parsed by Fluid. An `as="{blog}"` would be parsed as if you would have liked to make the name of the variable configurable. Rule of thumb: Curly brackets in `each`, none in `as`. Objects cannot be rendered by Fluid directly if they do not have a :php:`__toString()` method. The single properties of an object can be accessed with point-notation. If a string like `{blog.title}` appears, Fluid tries to parse it. Fluid expects the variable `blog` to be an object. Inside of this object it searches for a method named :php:`getTitle()`. The method's name is created by extracting the part after the point, capitalizing the first letter, and prefixing it with "get". With this convention, the call looks something like this: :php:`$blog->getTitle()`. The return value will replace `{blog.title}` in the template. In the same way, `{blog.description}` will be replaced with the description. Parsing this point goes recursively. That means Fluid can parse a string `{blog.administrator.name}` by calling a method that equals :php:`$blog->getAdministrator()->getName()`. .. note:: The return value is "tidied up" by :php:`htmlspecialchars()`. That protects from cross-site scripting (XSS) attacks. As soon as Fluid is done with the whole template, the result is added to the `Response` object. This is done in the :php:`\TYPO3\CMS\Extbase\Mvc\Controller\ActionController` by the call :php:`$body->write($this->view->render())`. .. todo: Again, let's mention that the user has to do that. This journey slowly comes to an end. The *request* has been fully answered by a corresponding action. The `Response` object carries the completely generated HTML content.