Fluid is the rendering engine used by TYPO3. It was transformed mainly by Claus Due to a standalone composer package that can be used with any framework. This standalone version replaced the fluid system extension in TYPO3 8 LTS and it is used in the Neos project as well. Originally posted on Twitter under the hashtag #Fluid24 Claus Due shared 24 Tips & Tricks for TYPO3 Fluid. This was well received by the TYPO3 community and since there were very useful practices for the daily work with TYPO3 and fluid I am going to simply republish Claus Tweets here. Obviously, all credit goes to him. I will not embed any Tweet but link to them so you can go there and like and share if you are into Twitter.
So, let's get started.
1. Assigning Variables
Starting with a small and easy example - Fluid can assign variables in a couple of handy ways. Useful among other things to reduce duplicate identical ViewHelper calls or defining variables before passing to f:render
.
<!-- Assigning variables //-->
1) <f:variable name="myvar" value="Hardcoded value"/>
2) <f:variable name="myvar">Larger expressions or static code</f:variable>
3) {f:variable(name: 'myvar', value: 'Hardcoded value')}
4) {my:expensiveViewHelper() -> f:variable(name: 'myvar')}
Then makes expression to use in f:render and others:
<f:render partial="MyPartial" arguments="{myvar: myvar}"/>
Rather than having to do, for example:
<f:render partial="MyPartial" arguments="{myvar: '{my:expensiveViewHelper()}'}" />
Which is *particularly* useful to avoid escaping inline syntax quotes!
2. Simple Math
Fluid can do simple math operations, making it easier to create things like progress status (or bars) based on raw input numbers.
<!-- Simple math -->
<f:variable name="floatPercent" value="0.25" />
Percent done: {floatPercent * 100}%
<f:variable name="total" value="12" />
<f:variable name="done" value="3" />
Progress: {total - done} remaining.
3. Disable Parsing
Fluid has a parsing modifier which can disable Fluid's parser in one template file, even if you use f:render
to render the template. Useful when including for example Mustache or namespaced XML template chunks.
<!-- Parsing enabled modifier -->
{parsing off}
This is a {{ mustache }}, <custom:xml>xml</custom:xml>
or other template which uses syntax that could be
confused with Fluid syntax. So we use {parsing off}
to completely disable Fluid in this particular file.
The template will not get parsed as Fluid even
when you render it with <f:render />. You can use
this to "include" a piece of such code into your
main Fluid template.
4. Default Output of optional Partials
There are two main ways you can render default output when an optional section or partial (or section in partial) is not found. Particularly useful when you render sections with a dynamic name coming from a variable.
<!-- Default values when partial/section not found -->
<f:render partial="Missing" optional="1" default="Partial 1 not found" />
<f:render partial="AlsoMissing" optional="1">
Partial 2 not found
</f:render>
<!-- Outputs: -->
Partial 1 not found Partial 2 not found
5. Dynamic variable Access
Fluid supports variable access with dynamic names/parts of name.
<!-- Recursive variable resolving -->
<f:variable name="array" value="{0: 'foo', 1: 'bar'}" />
<f:variable name="index" value="1" />
Variable "index" contains the array position we need.
Dynamic array index #{index}: {array.{index}} <!-- bar -->
<f:variable name="typeOne" value="This is type 1" />
<f:variable name="typeTwo" value="This is type 2" />
<f:variable name="selectedType" value="Two" />
Variable "selectedType" is a string that's part of a variable name.
Dynamic named variable: {type{selectedType}} <!-- This is type 2 -->
6. Ternary Conditions
Fluid supports ternary conditions using variables and simple numbers; but only using variables and simple numbers - e.g. no inline syntax or text. For that, use a normal condition instead.
<!-- Ternary condition -->
<f:variable name="variableToCheck" value="0" />
<f:variable name="option1" value="Option one" />
<f:variable name="option2" value="Option two" />
One of two options: {variableToCheck ? option1 : option2}
Fallback if variable not set: {option1 ? option1 : option2}
<!-- Outputs: -->
One of two options: Option two
Fallback if variable not set: Option one
<!-- Invalid: -->
{variableToCheck ? 'some text' : 'other text'}
7. Whitespace Trimming
Fluid has a whitespace-trimming ViewHelper, f:spaceless
, which removes whitespace between but not inside tags.
<!-- Removing meaningless whitespace -->
<f:spaceless>
<div>
<section>
<p>
Whitespace preserved inside tags
</p>
<p>But gets removed between tags</p>
</section>
</div>
</f:spaceless>
<!-- Outputs: -->
<div><section><p>
Whitespace preserved inside tags
</p><p>But gets removed between tags</p></section></div>
8. Disable Escaping in a Template
You can disable HTML entity escaping for an entire file, when you've sanitised all values beforehand or you render formats where potentially unsafe HTML tags is not a concern. Use it to avoid spamming f:format.raw
but be careful about when/where you use it!
<!-- Disabling HTML escaping for an entire file -->
{escaping off}
Any variables you render will not need to use f:format.raw
even if they contain special HTML entities. Useful when
you render formats other than HTML; or when you completely
trust (read: escaped before assigning to view) all your
variables that get output.
Use with care - caveat emptor!
9. Dynamic and optional Sections and Partials
You can render sections and partials with dynamic names and make it optional, to create different representations for objects that can have multiple types. When used with partials it is a lot easier to customise than a big template with switch/conditions!
<!-- Rendering sections or partials with dynamic names -->
A common use case is to switch the rendering of an object
based on the type the object has. Fluid supports using
f:render to render a section or partial where the name,
or part of the name, is taken from a variable or other
ViewHelper call. Useful when combined with a default
value in f:render to output when for example no type is
selected yet in the object.
<f:variable name="myType" value="Text" />
<!-- Switching {myType} to Image changes rendering -->
<f:section name="RenderAsText">
Text version of object
</f:section>
<f:section name="RenderAsImage">
Image version of object
</f:section>
<!-- Rendering section with partially dynamic name -->
<f:render section="RenderAs{myType}" optional="1" />
10. Global Import of Namespaces
In TYPO3 CMS, a global array of default Fluid namespaces exists and can be used to make your namespace available in any template without importing it; or to change/extend the Fluid namespaces the TYPO3 core adds.
// Globally registered fluid namespace
$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['my'] = ['MyVendor\\MyExtension\\ViewHelpers'];
// Makes "my" a namespace in Fluid, e.g. "<my:foobar>" resolves
// to class MyVendor\MyExtension\ViewHelpers\FoobarViewHelper
If you want a global namespace to look at multiple places for ViewHelper resolution you just add to the array like the core does for the f:
namespace:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['f'] = [
'TYPO3Fluid\\Fluid\\ViewHelpers',
'TYPO3\\CMS\\Fluid\\ViewHelpers'
];
The last item in the array will be looked up first. So in the above example f:form
will first resolve to \TYPO3\CMS\Fluid\ViewHelpers\Form
.
11. f:or
You can use f:or
to assign default/fallback values for variables. This can save you from having conditions in the template parts that contain the actual output of variables. Note that a fallback value is only assigned if the variable is precisely null
!
<!-- Giving variables default values if they are NULL -->
{class -> f:or(alternative: 'my-default') -> f:variable(name: 'class')}
{label -> f:or(alternative: 'No name') -> f:variable(name: 'label')}
{body -> f:or(alternative: 'Body not set') -> f:variable(name: 'body')}
<!-- Purpose: guarantee a value is filled for mandatory output -->
<div class="{class}">
<h4>{label}</h4>
<f:format.html>{body}</f:format.html>
</div>
<!--
Works well when your fields *must* always be output but don't necessarily
have a value, e.g. are TCA fields with no requirement for a value.
-->
Note that you can also assign new variables this way, in case you still need access to the old variable for conditions etc - just change the "name" of the f:variable
you create!
12. Override and Fallback in Namespace Imports
ViewHelper namespaces can be overlaid by importing to the same namespace alias multiple times. This can be used to merge namespaces or to select ViewHelper overrides from another package; on a per-template or global basis. See also 10. Global Import of Namespaces , works the same!
<!-- Registering multiple namespaces with same alias -->
{namespace f=Vendor\MyFluidOverrides\ViewHelpers}
{namespace my=Vendor\MyExtensionOne\ViewHelpers}
{namespace my=Vendor\MyExtensionTwo\ViewHelpers}
Namespace "f" is now overlaid with ViewHelpers from the
"Vendor\MyFluidOverrides\ViewHelpers" PHP namespace and
any class you create there (e.g. "VariableViewHelper")
will override the corresponding class from Fluid.
Namespace "my" is created with two namespace lookups,
first in "Vendor\MyExtensionTwo\ViewHelpers" and then
in "Vendor\MyExtensionOne\ViewHelpers" (last imported
gets checked first).
Use this to construct different versions of ViewHelpers
when you need to customise the behavior of them for a
given project. Or merge namespaces for convenience
when you use ViewHelpers from multiple packages.
In case of a collision the last imported namespace has the final word about which class name gets resolved.
13. Combine Sections and Variables
It makes sense to use f:section
and render it instead to reduce duplication. But it can also make sense to assign the output to a variable and use the variable instead, when the output is always the same. Combine this with previous f:render
tips for bonus!
<!-- Using f:render to make "blocks" of output -->
Rendering into a variable and then using that variable
can make the template more readable. This example is
VERY small; the bigger and more costly the rendering is,
the more sense it makes to use this trick.
<f:section name="Icon">
<span class="icon-{icon}"></span>
</f:section>
{f:render(section: 'Icon', arguments: {icon: 'twitter'}) -> f:variable(name: 'twitterIcon')}
{f:render(section: 'Icon', arguments: {icon: 'facebook'}) -> f:variable(name: 'facebookIcon')}
And use them as frequently as we want:
<div class="share">
Share here!
{twitterIcon -> f:format.raw()}
{facebookIcon -> f:format.raw()}
</div>
<div class="also-share">
Or here!
{twitterIcon -> f:format.raw()}
{facebookIcon -> f:format.raw()}
</div>
14. Elseif
Fluid has "if" and "else" behaviors, but it also does "else-if" which can be used to add a condition to an "else" node, making Fluid continue evaluating until an "else" is matched or a final "else" without condition is found.
<!-- Else-if structure -->
Fluid can render "if" and "else" and we all know how that
works - but Fluid can also render "else-if" type structures:
<f:if condition="{var} > 100">
<f:then> Overflow! </f:then>
<f:else if="{var} > 90"> Danger! </f:else>
<f:else if="{var} > 50"> Careful now. </f:else>
<f:else> All good! </f:else>
</f:if>
15. contentAs - Pass output of f:render to a Variable
I've been looking forward to sharing this one most of them all. You can use "contentAs" on f:render
to render the tag content and pass it as a variable. You can even nest f:render
inside f:render
to make both wrap and body render from partials/sections.
<!-- Passing rendered Fluid to a partial as variable -->
With "contentAs" on f:render you can make it render the tag
content and pass this to the partial as a variable you name.
This enables you to use f:render and partials to wrap output
in different ways. Consider the following example:
<f:render partial="MySpecialWrap" contentAs="content">
<div class="wrappable-content">
<h4>This content can be wrapped</h4>
<!-- Fluid can be used without restrictions here -->
</div>
</f:render>
And the following template source in the "MySpecialWrap"
partial template:
<!-- In partial MySpecialWrap.html -->
<div class="outerwrap wrap-foobar">
<section>
{content -> f:format.raw()}
</section>
</div>
And remember the previous f:render tips:
* The name of the partial can be dynamic, so you can make
this apply a dynamic type of wrapping.
* This also works with sections, of course.
* You an also combine this with "default" argument (but
obviously you cannot use the tag content as default)
16. RenderableInterface - render Objects directly in f:render
Fluid ships with an interface for objects you assign/pass as a template variable. Such objects can be rendered by f:render
and will receive the currently active RenderingContext (which contains things like current variables).
<!-- "Renderable" interface for objects -->
Normally, you build template output using variables that
you assign to the template and then output. But there is
an alternative approach, by using a delegate object to
render output. A Renderable is a cross between a ViewHelper
and template variable: but rather than parse the template
and detect a ViewHelper class to initialize and render,
you control the prepared object instance that gets rendered
and assign that instance to the template.
In PHP:
use \TYPO3Fluid\Fluid\Core\Rendering\AbstractRenderable;
use \TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
class MyRenderable extends AbstractRenderable
{
public function render(RenderingContextInterface $renderingContext)
{
// Any PHP execution you want. The RenderingContext
// will be the exact one that's active when you
// render the renderable (and contains variables etc.)
return 'My special rendering';
}
}
// then assign a view variable with an instance of MyRenderable.
In Fluid:
<f:render renderable="{renderable}" />
17. Data-Attributes in tag-based ViewHelpers
Tag-based ViewHelpers can support two ways of passing "data-" prefixed attributes. One is ideal for receiving such attributes from an external array. The other is much easier to write. Both can be combined, making one behave like fallbacks/defaults.
<!-- The "data" attribute on TagBasedViewHelpers -->
All ViewHelpers which use the AbstractTagBasedViewHelper
parent class, or internally use the TagBuilder from Fluid,
have the option of supporting arbitrary "data-*" attributes
in two ways:
1) <v:h data-foobar="baz" data-biz="biz">content</v:h>
2) <v:h data="{foobar: 'baz', biz: 'biz'}">content</v:h>
Assuming "v:h" outputs a "div" tag, both examples output:
<div data-foobar="baz" data-biz="biz">content</div>
The two methods can also be combined, but the more specific
"data-foobar" naming will override the corresponding value
if it is also passed in the "data" array.
The main idea is that no registration is required for each
"data-*" argument you pass, making it a lot easier for a
frontend developer to add new attributes without knowing
about the complex inline Fluid syntax and escaping that
would be necessary if passing all "data-*" attributes in
the "data" array.
18. Using a JSON file as fallback values for a template
Fluid has a "chained" VariableProvider which can delegate variable storage to multiple VariableProviders. Allows for example using a JSON file/url as fallback variables - which in turn works very well if your designers use a design system with JSON mocks.
// Using a JSON file as fallback values for a template
$existingVariables = $view->getRenderingContext()->getVariableProvider();
$jsonVariables = new \TYPO3Fluid\Fluid\Core\Variables\JSONVariableProvider();
// Supported JSON source types:
// 1) Local JSON file path
// 2) Remote JSON file path
// 3) JSON string source
$jsonVariables->setSource($jsonFilePathOrJsonSource);
$newVariables = new \TYPO3Fluid\Fluid\Core\Variables\ChainedVariableProvider(
[
$jsonVariables, $existingVariables
]
);
$view->getRenderingContext()->setVariableProvider($newVariables);
For obvious reasons this is most appropriate when prototyping and especially when working in teams (where it's not the same developer maintaining the JSON). It is less appropriate for production use (framework usually provides better means!).
19. Fluid Cache Warmer
Today's tip is for TYPO3 CMS only. A while ago I created a "Fluid cache warmer" which compiles all templates. Consists of:
- A CLI and library: typo3-cms-fluid-precompiler @github
- A TYPO3 extension and module: typo3-cms-fluid-precompiler-module @github
The first package can be used for a couple of purposes. Since it is decoupled from TYPO3 it will compile templates with just a partial environment, which means it is GREAT for use as a finishing touch in deployment scripts after you've flushed caches.
The second package simply integrates the ability to regenerate caches via the cache menu in TYPO3 CMS - so that after you've flushed system caches, you can be nice and also regenerate the entire Fluid cache to reduce pressure on your frontend.
20. Layouts
Today's tip is a two-parter about using Layouts; one to avoid redundant code and another to avoid failures when using dynamic Layout names.
<!-- Layouts: default and dynamic Layout names -->
First, f:layout has a little known default value for the
layout name. When your layout is named "Default" you can
omit the "name" argument:
<f:layout />
Second, the name of the layout can come from a variable,
but needs special care to ensure that a valid layout is
always selected, even if the variable has no value.
Don't:
<f:layout name="{layout}" />
But do:
<f:layout name="Custom{layout}" />
...and make sure you have a fallback "Custom" layout, in
addition to "CustomFoo", "CustomBar" or whichever values
the {layout} variable may have. In other words: treat
your dynamic layout name as a suffix for a filename.
Also applies to sections and partials - although there you have the "optional" argument and "default" (or tag content as default) to help guarantee a rendering w/o errors. That's not possible for Layouts.
21. render() vs. renderStatic()
A tip mostly for developers - since Fluid 2.4 when you create a ViewHelper, the render()
method is completely optional and you can instead implement only renderStatic()
- and no longer need to use the CompileWithRenderStatic
trait. Less is more!
22. Different ways of importing Namespaces
There are several ways you can import and ignore namespaces in Fluid, each one has different meaning. Ignoring is particularly useful when your template is XML, or when embedding SVG. Not shown: also possible in PHP via RenderingContext/ViewHelperResolver
.
<!-- Namespace imports and ignored namespaces -->
The following are the ways you can import Fluid namespaces
in template files and how they work:
1)
<html xmlns:f="typo3.org/ns/TYPO3/CMS/F…">
This approach means you can have tag-completion via XSD.
This particular notation *PRESERVES* the "html" tag.
</html>
2)
<html xmlns:f="typo3.org/ns/TYPO3/CMS/F…" data-namespace-typo3-fluid="true">
This approach is the same as above, but *REMOVES* the "html" tag due to the
"data-namespace-typo3-fluid" mark.
</html>
3)
{namespace foo=MyVendor\MyPackage\ViewHelpers}
This approach registers a single namespace. As with all curly-
braces based imports, the marker gets removed before output.
4)
{namespace foo}
This approach completely ignores any <foo:vh /> tags and will
allow them to be output including the namespace, without them
being seen as Fluid code. The particular notation here ignores
a single namespace.
5)
{namespace fo*}
This final approach ignores any tags like the one above, but
will match any namespace that starts with "fo" - e.g. will
match both "foo" and "fox".
23. Vision for changing the lowest level parsing in Fluid
Today is not a tip - it’s a cross between my long standing vision for changing the lowest level parsing in Fluid, and a report on research I’m currently doing. It’s work in progress but I’m close to making it work and work well.
24. Avoid Escaping of Quotes
Merry Christmas. By popular demand, the final tip is a strategy that combines several other tips, with the goal of avoiding escaping of quotes in Fluid. I’ll write a bit in this thread about the benefits of using that kind of strategy in your templates.
<!-- Reducing the need to escape quotes -->
In Fluid, when you nest ViewHelper expressions, you are required
to escape any nested quotes. This can quickly become difficult:
<f:render partial="Foobar" arguments="{
label: '{f:if(condition: lll, then:
\'{f:translate(key: \\'mylabel\\')}\',
else: \'foobar\')}
}" />
Since the number of escapes must always increase by one for every
time you use a new pair of quotes, it can quickly become too
difficult to manage.
There is however a way to avoid it: by assigning intermediate
variables and whenever possible, passing content as child node
instead of using a named argument.
{f:translate(key: 'mylabel')
-> f:if(condition: lll, else: 'foobar')
-> f:variable(name: 'label')}
<f:render partial="Foobar" arguments="{label: label}" />
Three tricks are used to reduce the need to use quotes:
1. Rather than using "then" on f:if to pass a translated label,
we pass it as the only child node to f:if - same meaning.
2. The arguments that do require quoting, we keep on the same
"escaping level" so they only require a single quote.
3. A variable is assigned so we can pass the result to other
ViewHelpers like f:render, without using any quotes at all.
You can combine this strategy with all the preceding tips about
using f:render, inline syntax, f:or and much more.
Happy coding and merry Christmas!
Benefit 1: Debugging friendly. If you have a problem with a partial or section failing to render, and you passed variables as escaped, nested inline syntax, debugging the variable can be difficult if you can't render the partial or section. With this: f:comment
, f:debug
variable.
Benefit 2: Easier to work with mock variables. For example, your prototype stage may receive a big set of mocked variables that you then pass to f:render
to render dummies during development. With this strategy, you can selectively replace each of those mock variables as you go.
Benefit 3: Because your variables can be assigned elsewhere than they are used, you can collect all the "nasty" syntax in one place and thoroughly document it with comments. And they become much easier to replace with variables from other sources, e.g. migrating TS object to VH.
Benefit 4: Most obviously though, this strategy makes the rest of the template far cleaner to read since most of it will simply be dealing with f:if
and f:render
to output or pass your assigned variables.
Please remember to head over to the originial Tweets by Claus to thank him for this list of useful Fluid tips & tricks. And please remember that every occurence of "I" in the texts above refers to him as well :).
And: Have fun with Fluid and TYPO3 in the new year!