TypoScript Harmony

Revisiting TYPO3 Phoenix Content Rendering

by Sebastian Kurfürst

for T3BOARD12

Usage: Slides are nested into other slides, press to go to the nested slides.

nested slide

Great, you were able to navigate to here!

There is no further slide below, so press to go to the next top-level slide.

Hint: at the bottom-right you see the navigation controls.

TypoScript in TYPO3 v4

Extensible Rendering Configuration

What's the underlying programming paradigm?

Programming Paradigms

Declarative vs Imparative

Procedural vs Object Oriented

not side-effect free: REGISTER

Not well-defined

however, very pragmatic

Use Cases

What do we expect from the new TypoScript?

Bound to nodes & stand-alone

sometimes, we want to output static text

sometimes, rendering of content

sometimes, one node is rendered multiple times

m:n relation nodes -- TypoScript objects

Extensible Rendering

stdWrap generalized: processors

match the hierarchical nature of nodes

Out-of-Band Rendering

Rendering single content elements

side-effect-free language

compact addressing needed

Node Path: /sites/flow3org/home[TYPO3.TYPO3:Page]
TypoScript Path: page/body

Bonus Features

also use it outside TYPO3, just with FLOW3

Inspiration Sources

TypoScript v4: extensibility, stdWrap

Fluid: HTML Templating

JavaScript: Prototype-based inheritance


jQuery: selecting nodes, fluent interface

CSS: set-based API; Selector Syntax

XPath: Traversal Operations

Usage Examples

Render a static page template


							page = TYPO3.TYPO3:Page
							page.body.templatePath = 'resource://TYPO3.Flow3Org/Private/Templates/Page/Default.html'
							page.body.sectionName = 'body'

							page.body.parts {
								mainMenu = TYPO3.TYPO3:MenuRenderer
								mainMenu.templatePath = 'resource://TYPO3.Flow3Org/Private/Templates/TypoScriptObjects/MainMenu.html'
								mainMenu.entryLevel = 1
								mainMenu.maximumLevels = 2
								subMenu = TYPO3.TYPO3:MenuRenderer
								subMenu.templatePath = 'resource://TYPO3.Flow3Org/Private/Templates/TypoScriptObjects/SubMenu.html'
								subMenu.entryLevel = 2
								subMenu.maximumLevels = 3
							}
							page.head {
								stylesheets {
									fromTemplate = TYPO3.TypoScript:FluidRenderer
									fromTemplate.templatePath = 'resource://TYPO3.Flow3Org/Private/Templates/Page/Default.html'
									fromTemplate.sectionName = 'stylesheets'
								}

								javascripts {
									fromTemplate = TYPO3.TypoScript:FluidRenderer
									fromTemplate.templatePath = 'resource://TYPO3.Flow3Org/Private/Templates/Page/Default.html'
									fromTemplate.sectionName = 'javascripts'
								}
							}
						

Output the Page Title


							// Static Title
							page.body.title = 'My Static Title'

							// make it dynamic:
							page.body.title = ${context.attr('title')}
						

Each TypoScript object operates on a context (a TYPO3CR Node).

${...} is an EEL Expression (it's like jQuery)

Outputting Content


							page.body.sections.main = TYPO3.TYPO3:Section
							page.body.sections.main.nodePath = 'main'
						

Adjusting Rendering Globally

Make the headline texts static


							prototype(TYPO3.TYPO3:Text).headline = 'Static Headline'
						

Wrap the headline texts might be more useful :-)


								prototype(TYPO3.TYPO3:Text).headline << 1.wrap(prefix: '-', suffix: '-')
							

Adjusting Rendering Locally

Make the headline texts inside all three-column elements static


							prototype(TYPO3.Flow3Org:ThreeColumn).prototype(TYPO3.TYPO3:Text).headline = 'Static Headline'
						

Make the headline texts inside the left column of all three-column elements static


								prototype(TYPO3.Flow3Org:ThreeColumn).left.prototype(TYPO3.TYPO3:Text).headline = 'Static Headline'
							

That's of course also possible with processors :-)

Creating a custom Content Element


							prototype(TYPO3.Flow3Org:TwoColumn) < prototype(TYPO3.TypoScript:FluidRenderer)
							prototype(TYPO3.Flow3Org:TwoColumn) {
								templatePath = 'resource://TYPO3.Flow3Org/Private/Templates/TypoScriptObjects/TwoColumn.html'
								left = TYPO3.TYPO3:Section
								left.nodePath = 'left'
								right = TYPO3.TYPO3:Section
								right.nodePath = 'right'
							}
						

Questions so far?

OK, then let's dive into the internals :-)

Internals

TypoScript Programming Paradigm

TypoScript is a hierarchical, prototype-based, object oriented, declarative, side-effect free language.

Basics

  • The Parser parses the TypoScript markup and generates an intermediate representation (IR)
  • The Runtime takes the IR and controls rendering.
  • The Runtime instanciates the TypoScript Objects, lazily as needed.
  • Every TypoScript Object is implemented by a PHP class
  • A TypoScript Object works on a Context (a TYPO3CR Node) and transforms it somehow, i.e. renders it.
  • the context is a stack and can be manipulated using push/pop

revisited: Rendering a list of Nodes


							// Setting implementation class name
							prototype(TYPO3.TypoScript:CollectionRenderer).implementationClassName = 'TYPO3\\TypoScript\\TypoScriptObjects\\CollectionRenderer'
							prototype(TYPO3.TypoScript:Case).implementationClassName = 'TYPO3\\TypoScript\\TypoScriptObjects\\CaseTsObject'

							prototype(TYPO3.TypoScript:CollectionRenderer) {
								collection = ${context.children()}
								itemRenderer = TYPO3.TypoScript:Case
							}

							// a CASE TypoScript object maps a TYPO3CR Node to a certain TypoScript object based on a condition.
							// It has a list of MATCHERS which are evaluated in-order
							// Each matcher consists of a "condition", which is in most cases an EEL expression,
							// and a TypoScript type which shall be used if the condition is TRUE.

							// Catch-all condition for the default case
							prototype(TYPO3.TypoScript:Case).matchers.999999999999 {
								condition = ${true}
								type = ${context.attr('_contentType')}
							}
						

Eel

is a JavaScript-like syntax for calling methods and functions


							${foo.bar}         // Traversal
							${foo.bar()}       // Method call
							${foo.bar().baz()} // Method call
							${foo.bar("arg1", true, 42)} // Method call with arguments

							// true, false, numbers are all valid eel expressions

							${12+18.5}                      // you can calculate as well
							${foo == bar}                   // ... and compare

							${foo.bar(12+18.5, foo == bar)} // and of course also use it inside arguments

							${[foo, bar]}           // Array Literal
							${{foo: bar, baz:test}} // Object Literal
						

FlowQuery

is like jQuery for FLOW3


								
# output text property of node ${context.property('text')}
# find all parent nodes ${context.parents()}
# find all parent nodes and add the current node to the selected set ${context.parents().add(context)}
isAtLeastOnThirdLevel = ${context.parents().count() >= 3}
${context.children('left').first()} ${context.children().filter('left').first()}
isOfTypeText = ${context.is('[instanceof TYPO3.TYPO3:Text]')} # returns boolean value

Lazy Evaluation

Out-of-band rendering

Easily possible now :)

Questions?

OK, then I have some :-)

Discussion

"static TypoScript" / Embedding in TYPO3

Do we still need Variables or constants?

How can we implement the reference operator?

How to use TypoScript for hierarchical configuration? Configure two plugin instances on the same page differently

Should there be TS objects for accessing repositories?

what about Conditions?(problematic for Out of Band-rendering; influences caching)

THE END

BY Sebastian Kurfürst

Slides based on reveal.js