The declarative nature of Fusion
Fusion is different from a programming language like JavaScript or PHP. You also have an input, the evaluation of your declarations and the generated output - most probably in the form of rendered HTML markup. But it's not about the ordering of statements and control flow through a program. In Fusion you'll just have declarations of properties that will be evaluated for a given path. And these declarations can be overridden to adapt for different situations:
- Want to change the image width of a teaser box if it's in the sidebar? No problem.
- Your lovely blog package is only doing 98% of what you need? Since it uses Fusion it should be easy to override a bit here and there.
So let's have a look back and see what we can use to define a Fusion property:
Simple values
book = "<book><title>Don't panic</title></book>"
answer = 42
enableImprobabilityDrive = true
Here we define three different paths title
, answer
and enableImprobabilityDrive
as simple values. There's not much going on in terms of logic here. The evaluated value for all the paths is just the written literal value.
Expressions
answer = ${Math.floor(85 / 2)}
Any path can as well be an expression, which will give us much more power to use dynamic values like calculations. Have a look at Fusion In Action - Part 1 to see more expressions in action.
Objects
Fusion
book = Template {
templatePath = 'Book.html'
title = "Don't panic"
}
Book.html
<book><title>{title}</title></book>
Output
<book><title>Don't panic</title></book>
Here we replaced the declaration for book
with an object of type Template
. We already saw that in Part 1: it will render a Fluid template which uses a variable title
that is declared to be the simple value "Don't panic"
.
Now for a very interesting question: When is it allowed to use an expression instead of a simple value? And where can we declare an object instead of an expression or value? The answer (try to remember this): In Fusion the different forms of declarations are interchangeable. That means you could take a string value and replace it with an object. How cool is this?
This works because all different forms produce a value when evaluated. It doesn't matter if the final string value came from a template, some fancy expression or was hard-coded as a simple value.
The source of an object: prototypes
We already covered basic expressions and simple values. But let's see where objects in Fusion actually come from.
Every object that is used in Fusion is based on a prototype definition. It's like a building plan for instances of that object. We can declare properties with default values on prototypes which will be used for all the instances of that object type:
Fusion
prototype(Array) {
hello = 'Hello'
}
output = Array {
world = ' world!'
}
Output
Hello world!
In this example the path output
has the hello
property already declared from the Array
prototype, which holds the default declarations for all object instances of type Array
. As already seen in Part 1, the Array
object renders all properties and combines them into one string.
Fusion
prototype(Array) {
hello = 'Hello'
}
output = Array {
hello = '¡Hola!'
}
Output
¡Hola!
You see that an object instance will override properties of the same name from a prototype.
But changing all the arrays is maybe not so useful. What about templates? If we would render a lot of books, we always need to define the same templatePath
:
output = Array {
book1 = Template {
templatePath = 'Book.html'
title = "The Hitchhiker's Guide to the Galaxy"
}
book2 = Template {
templatePath = 'Book.html'
title = "The Restaurant at the End of the Universe"
}
}
Let's move that to the Template
prototype definition:
prototype(Template) {
templatePath = 'Book.html'
}
output = Array {
book1 = Template {
title = "The Hitchhiker's Guide to the Galaxy"
}
book2 = Template {
title = "The Restaurant at the End of the Universe"
}
}
The book paths will now have a templatePath
property pre-defined with the value Book.html
. This will happily render two books with the same template.
Note, that it's probably not useful to manipulate the default prototypes for the base objects like Array
or Template
in a real project. After all you want to be able to render other things as books, too.
Prototype extension: Make your own object
This is why you can make up your own objects easily by extending from an existing prototype:
prototype(Book) < prototype(Template) {
templatePath = 'Book.html'
}
output = Book {
title = 'Mostly harmless'
}
The new Book
object type extends from Template
and declares a property templatePath
. If we declare a Book
object for the path output
, it will have all the declarations of the Book
prototype and the Template
prototype applied. The order will be the Book
instance first, then the Book
prototype definition and then the Template
prototype definition.
Custom prototypes are crucial to declare the rendering of different node types in Neos and to create custom definitions for components of a page that can be re-used easily. Instead of having one large template and declaration, the rendering is split in several prototypes that are configured with different properties. All parts of a website like menus, content elements, teasers and whatever's more can be implemented as separate components with Fusion prototypes.
Prevent collision, use namespaces
There could be a lot of packages implementing something like a Teaser
or Menu
object type. This is why every object type in Fusion begins with a namespace. In fact all the namespaces were left out in Part 1 to simplify the examples.
The convention is to use the package key as the prefix to prevent collisions between the same object type in different packages:
prototype(Acme.Demo:Book) < prototype(Neos.Fusion:Template) {
templatePath = 'MyMenu.html'
}
See how we use Neos.Fusion:Template
instead of just Template
here? That's the full object type for the Template object in the Neos.Fusion package. The package key of our example site package is Acme.Demo
, so it should be used as the namespace for our own object types.
When using the object in a declaration we need to specify the full object type:
output = Acme.Demo:Book {
title = 'Mostly harmless'
}
Inside a Fusion file you can import namespaces with an alias name for shorter declarations:
namespace d=Acme.Demo
output = d:Book {
title = 'Mostly harmless'
}
With the namespace declaration we can now write d
whenever we mean Acme.Demo
.
If no namespace is given, Neos CMS will use the default namespace Neos.Neos
in a Fusion file. So declaring an object Page
will mean Neos.Neos:Page
for the actual object type.
A collection of nodes
As we often deal with collections when rendering nodes, Fusion comes with a Collection
object type that iterates over a list of something and renders the items using a rendering definition.
But first let's get back to the last example of Part 1 that showed a simple page rendering by iterating over all nodes on a page in a Fluid template.
Rendering multiple nodes with Fluid
Context
Fusion
page = Template {
templatePath = 'Main.html'
node = ${node}
childNodes = ${q(node).find('main').children()}
}
Main.html
<html>
<title>{node.properties.title}</title>
<body>
<h1>{node.properties.title}</h1>
<f:for each="{childNodes}" as="childNode">
<h2>{childNode.properties.title}</h2>
<p>{childNode.properties.text}</p>
</f:for>
</body>
</html>
The list of nodes is declared as a property childNodes
by taking all children of the ContentCollection node main
. This approach uses a f:for
loop in Fluid to render the content nodes with the same markup (despite of their node type). But in Neos it's very important to render different node types with specific markup. Of course this should also be easy to extend or adjust for new node types.
Rendering multiple nodes with a collection
Extending and customizing an existing Fluid template from another package is not so easy and could mean to duplicate a lot of code. So let's use the Collection object to render the nodes with a simple Acme.Demo:Content prototype that declares the rendering of a content element. That way it's easy to customize the rendering of the content later by switching the template or adjusting properties on the prototype.
Fusion
prototype(Acme.Demo:Content) < prototype(Neos.Fusion:Template) {
templatePath = 'Content.html'
contentNode = ${childNode}
}
page = Neos.Fusion:Template {
templatePath = 'Main.html'
node = ${node}
content = Neos.Fusion:Collection {
collection = ${q(node).find('main').children()}
itemName = 'childNode'
itemRenderer = Acme.Demo:Content
}
}
Content.html
<h2>{contentNode.properties.title}</h2>
<p>{contentNode.properties.text}</p>
Main.html
<html>
<title>{node.properties.title}</title>
<body>
<h1>{node.properties.title}</h1>
{content -> f:format.raw()}
</body>
</html>
Output
<html>
<title>About us</title>
<body>
<h1>About us</h1>
<h2>Hello world!</h2>
<p></p>
<h2></h2>
<p>Welcome to Fusion...</p>
</body>
</html>
Instead of just passing the list of nodes to the template in a variable, we now declare the fully rendered content in the content
property. When this property is evaluated in the template it will contain the generated markup. Because Fluid will escape any markup by default we have to use the f:format.raw
view helper here.
The content
property is declared to be rendered by a Neos.Fusion:Collection
object that needs some configuration to know about the collection to render, the item name for the context and how to render each individual item. The itemRenderer
property does that by using a custom object type Acme.Demo:Content
. Instead of an object we could as well use a simple value or expression here.
For each item in the collection
property - which is defined the same way as before - the Collection
object will make the item available as a context variable of the configured itemName
and evaluate the property itemRenderer
.
With the given example nodes, the Acme.Demo:Content
object will be evaluated first for the Headline node /home/main/node1 and then for the Text node /home/main/node2. The context variable childNode
will hold a different node each time and is passed to the template as a variable contentNode.
We could simplify the variable names by overriding the node
context variable for each item:
Fusion
prototype(Acme.Demo:Content) < prototype(Neos.Fusion:Template) {
# ...
node = ${node}
}
page = Neos.Fusion:Template {
# ...
content = Neos.Fusion:Collection {
# ...
itemName = 'node'
}
}
This will work because the Collection
object manages to restore the original node
context variable after evaluating each item. So after rendering the content
part of the page, the node
context variable will hold the page node again.
Collections beyond content
A Collection is not only useful to render content elements, but whenever a rendering should be done based on a list of items. Let's look at the rendering of a title tag that outputs the page titles of all nodes until the root page, so we get an output like The Hitchhiker's Guide - Books - Home
for the following example nodes:
Context
Fusion
page = Neos.Fusion:Template {
templatePath = 'Main.html'
title = Neos.Fusion:Collection {
collection = ${q(node).add(q(node).parents())}
itemName = 'node'
itemRenderer = ${q(node).property('title') + ' - '}
}
}
Main.html
<html>
<title>{title}</title>
<body>
...
</body>
</html>
Output
<html>
<title>The Hitchhiker's Guide - Books - Home - </title>
<body>
...
</body>
</html>
The collection
property is declared using an expression that takes the current node and adds all the parent nodes to a FlowQuery result. All page nodes are exposed in the context variable node
and rendered by the expression in the property itemRenderer
. It get's the title
property from the node and adds the string ' - '
after each item.
Did you spot the error? The title ends with "... - Home - "
instead of just "... - Home"
. That's uncool and we can do better with Fusion. You can access the current iteration information by defining the property iterationName
on the Collection object. This allows us to check for the first or last item and change the output accordingly:
Fusion
page = Neos.Fusion:Template {
templatePath = 'Main.html'
title = Neos.Fusion:Collection {
collection = ${q(node).add(q(node).parents())}
itemName = 'node'
iterationName = 'iterator'
itemRenderer = ${q(node).property('title') + (iterator.isLast ? '' : ' - ')}
}
}
Output
<html>
<title>The Hitchhiker's Guide - Books - Home</title>
<body>
...
</body>
</html>
The expression iterator.isLast
returns true
for the last item and by using a condition of the form condition ? thenExpression : elseExpression
we'll add an empty string only for the last item. This works as expected and yields the desired output.
Conditional rendering using Case
Rendering content with the Collection object is already pretty nice but we still lack the possibility to change the rendering for the specific node types. A Headline node should render something different as a Text node.
The way the Neos rendering does this per default is to have an according prototype definition for each node type. If a node needs to be rendered, a prototype of the node type name will be used to evaluate the markup of that node. Since the content repository also uses namespaces for the node type names this is a perfect match:
Fusion
prototype(Neos.NodeTypes:Headline) < prototype(Neos.Fusion:Template) {
templatePath = 'Headline.html'
title = ${q(node).property('title')}
}
Headline.html
<h1>{title}</h1>
The prototype for node type Headline extends from the Template prototype and renders a node by declaring a title
variable for the template based on the value of the title
property of the node using an expression.
But how can we switch the object type to render different prototypes based on the node type name?
Fusion has a Case
object that can change the rendering based on conditions. The Case
object works just like Array
by declaring the cases as nested properties. But only one case will be evaluated, based on the first condition
that matches.
Let's see how we can declare the rendering of the Headline node type using a Case object:
Fusion
page = Neos.Fusion:Template {
# ...
content = Neos.Fusion:Collection {
collection = ${q(node).find('main').children()}
itemName = 'node'
itemRenderer = Neos.Fusion:Case {
headline {
condition = ${q(node).property('_nodeType.name') == 'Neos.NodeTypes:Headline'}
type = 'Neos.NodeTypes:Headline'
}
}
}
}
We changed the custom prototype in itemRenderer
to a Case object and declared one case headline
that uses a expression to evaluate the condition
. The node type name of the current node in the iteration is compared against the string 'Neos.NodeTypes:Headline'
and if it matches the item will be rendered using a 'Neos.NodeTypes:Headline'
object.
Let's add a new case for the Text node type to pull everything together:
Fusion
page = Neos.Fusion:Template {
# ...
content = Neos.Fusion:Collection {
collection = ${q(node).find('main').children()}
itemName = 'node'
itemRenderer = Neos.Fusion:Case {
headline {
condition = ${q(node).property('_nodeType.name') == 'Neos.NodeTypes:Headline'}
type = 'Neos.NodeTypes:Headline'
}
text {
condition = ${q(node).property('_nodeType.name') == 'Neos.NodeTypes:Text'}
type = 'Neos.NodeTypes:Text'
}
}
}
}
This will render the nodes using the Collection object and switches the object type to render each item using the two cases. We already get a rendering that can output different markup based on the node type, but it's also some work to declare all the cases.
There's one very important catch in each case: the type
property will be evaluated to a string and a Fusion object of that type will be used to render the matched case. This is why we use string quotes in type = 'Neos.NodeTypes:Headline'
instead of declaring an object as we did with itemRenderer = Acme.Demo:Content
.
The good thing about this is that we can also use an expression to declare the type
property based on the node type name. So instead of adding a definition for every node type in the system (which can be quite tedious) we can have some very clever automatism with just 4 lines of Fusion:
Fusion
page = Neos.Fusion:Template {
# ...
content = Neos.Fusion:Collection {
# ...
itemRenderer = Neos.Fusion:Case {
default {
condition = TRUE
type = ${q(node).property('_nodeType.name')}
}
}
}
}
The condition
property for the default
case will always evaluate to true. Think of it like a catch-all that will do some reasonable default. The type
property is declared with an expression and will evaluate to the node type name of the current node. This declaration means that by default a node will be rendered with a prototype of the same name as the node type.
Don't panic if you have trouble understanding this at first. In Neos this is already set up to work for you. Everything you have to do is customize your prototype definitions for the different node types. We'll cover this in more depth in the next part.
To be continued...
This guide showed some new powerful features of Fusion and gave a deeper introduction into the content rendering of Neos. In fact, if you could follow the examples, it shouldn't be a problem for you to understand the details inside the Neos default Fusion.
But beware, you're not a Fusion master yet! There are some details left to leverage the full potential. In Part 3 we will have a look at:
- The structure of Fusion files in Neos CMS
- Flexible positioning with @position
- Using @context to manipulate the context (a.k.a. using the force)
- Accessing object properties with this from expressions
- How Fusion objects come to life with @class
I hope you enjoyed the second guide to the Fusion universe. Leave a comment for feedback and please share the article if you liked it.
Update (March, 2018): Renamed TypoScript 2 to Fusion