Hitchhiker's guide to Neos Fusion - Part 2

This guide will dive deeper into how Fusion drives the rendering in Neos CMS. We'll have a look at prototypes - a very powerful concept to create your own rendering components. There will be collections, conditions and a final example that is very close to the actual content rendering in Neos. This guide is a little bit longer, so make sure to take a towel with you.

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

comments powered by Disqus