Skip to content

Instantly share code, notes, and snippets.

@hlubek
Created December 10, 2013 11:45
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hlubek/7889386 to your computer and use it in GitHub Desktop.
Save hlubek/7889386 to your computer and use it in GitHub Desktop.
TypoScript 2 guide

Hitchhikers guide to TypoScript 2

The old days

Do you remember the good old days?

What is TypoScript? Various myths and speculations exist about that, well, programming language? No, it never was a programming language. Though it could be used to express logic. Is it a configuration language then? It's often used to configure settings, but the scope is certainly blurry.

The power of TypoScript - if used well - is the declarative approach of expressing the rendering and execution of various parts of the system. And a declaration in TypoScript is never final, but can be changed at a later time by another extension or template.

One major problem of the TypoScript implementation in TYPO3 CMS is the limited extensibility and the undefined behavior of a given snippet of code. TypoScript is not always evaluated directly, but the final definition of the so called setup are given to PHP code to process it. And most of the time this processing behaviour differed quite a lot.

So, what is TypoScript 2?

To prevent confusion, we started with a clear idea of what TypoScript in Neos should be and what it shouldn't be:

TypoScript 2 is a declarative and extensible language to integrate system components.

The main use case in Neos is the rendering declaration using TypoScript 2. With a strict MVC approach a controller finds the relevant node given by a URI and uses a TypoScript view to render a frontend representation of that node.

But we had more than just rendering of websites in our mind. Imagine an e-commerce application that should be re-used and extended by others. Instead of having templates that are used in a fixed way, a TypoScript driven view would be able to control the output in a fine-grained and extensible way.

Hello world, TypoScript 2

output = 'Hello world!'

Output:

Hello world!

That looks familiar with a lot of programming languages. You could write the same line in Python, Ruby, JavaScript and other. But what does it mean here? We declare a path output that is assigned a simple value of a string Hello world.

Variables in TypoScript 2 are called paths. These are evaluated from the outside. For the examples we just take the name output as the path we are rendering.

Let there be objects

Simple values are a bit simplistic for the problems we have at hand. So TypoScript 2 offers objects as the main building blocks and re-using code. Think of them as components that can be a complete basic page, a menu or the rendering instructions for a node.

output = Array {
    hello = 'Hello'
    world = 'world!'
}

Output:

Helloworld!

The Array object will allow nesting and is evaluated by rendering all its children and combining their output. This is done literally, so we miss a space between the words here.

The paths hello and world are properties of the object in this case. We can set an individual property with the following syntax:

output.world = ' world!'

This should result in the expected output of Hello world!.

Whenever you see the brackets "{" and "}" in TypoScript, it's only a shorter form of writing the paths in the full form:

output = Array
output.hello = 'Hello'
output.world = ' world!'

There is only a handful of object types that ship with the TypoScript 2 core. This makes the language comprehensible and easy to learn.

Express yourself

With objects we can declare structures. And with simple values we can declare values like strings, boolean values or numbers.

Yet, sometimes we want to have something more expressive at hands to change a behaviour depending on some condition or to specify the processing of some property.

output = ${Array.join(['Hello', 'world!'], ' ')}

Output:

Hello world!

Wow, that looks like we are actually programming here!

Indeed we introduced an expression language called Eel in TypoScript 2. It's the perfect companion for the tricky and individual cases where we wished for something more than objects and simple values. It's a subset of the JavaScript language that allows to write any kind of expression. That means no manipulation of variables, but anything that expresses a value.

In the example we use a helper with name Array that comes pre-defined with Neos. These helper objects provide a standard library of functions to work with strings, array, dates and other data in the system. And it's easy to write custom helpers to extend the functions that are available in Eel.

We need a context

Expressions are really powerful if we have to deal with data coming from nodes or other objects. But where does that data come from?

For the frontend rendering in Neos we have the node that was represented by a URI like /home/about-us.html. This node is given to TypoScript in the so called context and is accessible in expressions and by the objects themselves.

Context:

node = {Page node: About us}

TypoScript:

output = ${'<title>' + node.properties.title + '</title>'}

Output:

<title>About us</title>

An expression can access public properties and call methods of an object context variable. For security reasons expressions in the Neos TypoScript view have a protected context that does not allow arbitrary method calls on objects. Imagine someone writing a line of TypoScript that destroys a node while rendering it.

Working with nodes

In the content repository of Neos everything is a node. The nodes are nested and form a tree. Navigating between the nodes using the node API of the content repository can become complicated.

We wished to have some simple tools for finding, filtering and navigating through the nodes in the repository. With FlowQuery we ported the ideas of the successful jQuery JavaScript library to the server-side. Isn't the node tree like a DOM?

Context:

node = {Page node: About us}

TypoScript:

output = ${q(node).parents().first().property('title')}

Output:

Home

This expression gets the direct parent of the node and reads the title property. With FlowQuery we can achieve an individual output of the node structure without any PHP programming or writing complex queries in SQL.

Processors

Sometimes we're just not happy with what we have. This seems to be the human nature, but TypoScript 2 has a solution. It's called processor and can be applied to any value.

Imagine the expression for rendering the title of the direct parent of a node:

output = ${q(node).parents().first().property('title')}

What if we wanted to wrap this with a <strong> tag in our site package?

output = ${'<strong>' + q(node).parents().first().property('title') + '</strong>'}

This works, but we need to copy the whole expression. A processor can be applied to a path to alter it's value:

output.@process.strongTag = ${'<strong>' + value + '</strong>'}

The name strongTag is arbitrary here and could be just a number. It's desirable though to give processors a distinct name.

A page test drive

So how could we declare the page output in Neos? As we have the power of Fluid available with the Template object, we could just forward the node context variable to the template.

Context:

node = {Page node: About us}

TypoScript:

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>

This should render the basic HTML for the page and the nodes in the main content collection. But of course it only works if all the child nodes have a title and text property. What about supporting different node types and rendering them differently?

We need some additional features of TypoScript 2 to have a fully working page rendering.

Objects and prototypes

Every object that is used in TypoScript 2 is based on a prototype definition. We can declare properties with default values on prototypes, which will be used for all the instances of that object:

prototype(Array) {
    hello = 'Hello'
}

output = Array {
    world = ' world!'
}

Output:

Hello world!

In this example the output path had the hello property already declared from the Array prototype, which holds the default declarations for all objects of type Array.

But changing all the arrays is maybe not so useful. What about templates? Always forwarding the node context to the template would be annoying.

prototype(Template) {
    node = ${node}
}

page = Template {
    templatePath = 'Main.html'
}

The page path will now have a node property pre-defined with the expression ${node}. Note, that it's probably not useful to manipulate the default prototypes for the base objects like Array or Template in a real project.

Make your own object

This is why you can make up your own objects easily by extending from an existing prototype:

prototype(Footer) < prototype(Template) {
  node = ${node}
}

output = Footer

The new Footer object type extends from Template and declares a property node. If we assign the Footer object to the output path it will have all the declarations of the Footer prototype and the Template prototype applied.

This is especially handy to declare the rendering of node types and to create custom definitions for components of a page that can be re-used easily.

Prevent collision, use namespaces

Of course there could be a lot of packages implementing a Teaser or Text node type. This is why we implemented namespaces from the beginning. We have the convention to use the package key as the prefix of a node type or object type in Neos to prevent these collisions.

prototype(Acme.Demo:Teaser) < prototype(TYPO3.Neos:Template) {
    title = ${q(node).property('title')}
    text = ${q(node).property('text')}
}

A collection of nodes

As we deal with collections often when rendering nodes, TypoScript comes with a Collection object type that iterates over a list of something and renders the items using

tbd.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment