The Rendering Engine
Catapult is able to render several document formats, such as simple text, JSON, and every XML-based format such as XHTML, RSS or SVG. In the page source you may use the Spring Expression Language (SpEL) for accessing data from the current request or the application. A SpEL expression within Catapult must be surrounded by the delimiters #{ and }. Thus a simple text page containing the following line
You came from #{header['referer']}
will output the text “You came from ” and the referer for that page, which stems from the bean named header. A comprehensive list of beans provided by Catapult can be found in the chapter TODO.
Templating
For all XML-based formats Catapult offers a powerful templating engine that allows re-use of page skeletons (e.g. for page layouts) and page partials. The templating engines makes heavily use of XML namespaces. The following three kinds of namespaces will be distinguished:
- format namespaces, such as
http://www.w3.org/1999/xhtmlfor XHTML – these will be copied to the output. However, they are important to support and extend the semantics of certain elements such as<form>elements, i.e. the XHTML namespace cannot simply be omitted. - Catapult namespaces for additional functionality, which are
- the core namespace
http://www.catapultframework.org/xml/core - the param namespace
http://www.catapultframework.org/xml/param - the default-param namespace
http://www.catapultframework.org/xml/default-param
- the core namespace
- template namespaces which identify directories for files that act as page templates – these consists of paths starting with a slash, for example simply
/or/matches
We will shortly see all three namespace kinds in action. First start with a very simple example.
Let’s suppose you are going to create a page that defines the base layout for all pages of your web application. A very simple base XHTML page (named layout.html) could look like this:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>#{#title}</title>
</head>
<body>
<div id="main" />
</body>
</html>
There are two aspects to mention here: First, the page references a SpEL variable title within the title element, whose definition we will see in a minute. Second, it contains an empty div element having the ID main.
Now some other page may use this layout.html as its base in the following way:
<root:layout
xmlns="http://www.w3.org/1999/xhtml"
xmlns:root="/"
title="Hello">
<root:main>
<h1>Hello, world!</h1>
</root:main>
</root:layout>
Several things will happen here:
- The template namespace prefix
rootis bound to the root directory/(note that there is no host part etc). The toplevel elementroot:layouttherefore refers to the filelayout.htmlin the root directory. The extension.htmlwill derived from the extension of the current page. - The attribute
title="Hello"assigns the string value “Hello” to the variabletitle. This way you can pass variable values to a template. - The element
root:main(as a descendant ofroot:layout) overrides the contents of the element having the IDmain
The resulting page rendered by Catapult is
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Hello</title>
</head>
<body>
<div id="main">
<h1>Hello, world!</h1>
</div>
</body>
</html>
The page referred to by the root:layout element doesn’t necessarily have to be the top-level element of a page. When used within a page the constructs behaves like an include. (Note: the underlying template mechanism is the same in both use cases.)
Here is a simple example. File logo.html:
<div xmlns="http://www.w3.org/1999/xhtml" class="logo">
<span>The L<sub>o</sub>g<sup>o</sup></span>
</div>
This file then may be used in different pages (or even several times within the same page) by writing
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:root="/">
...
<body>
<root:logo />
</body>
</html>
or (by combining the two templates)
<root:layout
xmlns="http://www.w3.org/1999/xhtml"
xmlns:root="/"
title="Hello">
<root:main>
<h1>Hello, world!</h1>
<root:logo />
</root:main>
</root:layout>
Again you may pass variables to the included partial (simply by assigning attributes) or override parts of the partial by using the corresponding IDs.
There is the special element super in the catapult core namespace that inserts the overridden contents. A typical use case is
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="head">
<sript type="text/javascript" href="main.js" />
</head>
...
</html>
and then
<root:layout
xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://www.catapultframework.org/xml/core"
xmlns:root="/">
<root:head>
<c:super />
<script type="text/javascript" href="user.js" />
</root:head>
...
</root:layout>
It is possible to use cascaded templates. For example if the last code snippet resides in the file user.html then a third page address.html may use user.html as its base template by:
<root:user
xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://www.catapultframework.org/xml/core"
xmlns:root="/">
<root:head>
<c:super />
<script type="text/javascript" href="address.js" />
</root:head>
...
</root:user>
The rendered page will contain links to all three javascript files (i.e. main.js, user.js, and address.js). Of course it is possible to override elements from any of the base templates in the cascade.
In addition to template files Catapult provides so called local templates. This is especially useful if you have to use the same template multiple times within a single page only. In this case the definition of the template may be placed directly in the page that uses the template.
A local template has to be put inside a c:def element like this (the prefix c denotes the Catapult core namespace as usual):
<root:layout
xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://www.catapultframework.org/xml/core"
xmlns:root="/">
<c:def name="logo">
<span>The L<sub>o</sub>g<sup>o</sup></span>
</c:def>
...
</root:layout>
The value logo of the name attribute defines the name of the local template. The c:def element itself doesn’t render anyhing, it is just a definition. To insert the template you have to use the name of the template in the core namespace (in this case c:logo) like this:
<root:layout
xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://www.catapultframework.org/xml/core"
xmlns:root="/">
<c:def name="logo">
<span>The L<sub>o</sub>g<sup>o</sup></span>
</c:def>
<root:main>
<c:logo />
... <!-- further content -->
<c:logo />
</root:main>
</root:layout>
In the resulting page each of the two c:logo elements will be replaced with the contents of the c:def element. Of course, as with other templates, you can pass parameters to a local template by using attributes.
Summary
- A
ns:nameelement will be replaced with the contents of a filename.ext that has the same extension ext as the referencing file and that resides in the directory specified by the namespace for the prefixns. - Any (non-prefixed) attribute for such an element defines a variable for usage in the referenced file and that has the attribute name as the variable name and the attribute value as the variable value.
- The children of
ns:namemay be elements in the same namespace of the formns:id1,ns:id2, etc. The contents of each of these elements replaces the contents of a corresponding element in the template file (name.ext) whose ID is the local name of the referring element (i.e.ns:id1replaces the contents of the element havingid="id1"). - The special element
c:superinserts the overridden content of the current element. - Local templates can be defined using the
c:defelement.
Conditions
For the conditional rendering of elements in an XHTML or XML page Catapult provides the attribute if in Catapult’s core namespace http://www.catapultframework.org/xml/core. The value of the attribute must evaluate to a boolean that determines whether the element will be rendered or not.
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://www.catapultframework.org/xml/core">
...
<div c:if="#{#game.over}">
Game over!
</div>
...
If the expression #game.over evaluates to false then the div element will not be rendered.
Note that a null will have the same effect as a false. However, when using boolean operators then neither of the operands can be null. This means
<div c:if="#{#bar}">Bar: #{#bar}</div>
<div c:if="#{#baz}">Baz: #{#baz}</div>
will render nothing if both of the variables bar and baz don’t have a value. In contrast
<div c:if="#{#bar and #baz}">Both present: #{#bar} and #{#baz}</div>
will cause an exception in this case.
Since there is no c:else attribute, the else case has to be be expressed by using an c:if with the negated expression.
<div c:if="#{#game.over}">
Game over!
</div>
<div c:if="#{!#game.over}">
Your turn!
</div>
The double evaluation of a complex or expensive expression can be prevented by defining a local variable (see Local variables).
For attributes it is far easier to prevent their output on an rendered XML element. The simple rule is that an attribute whose value is null will not be rendered. For example
<div class="#{#answer != 42 ? 'error' : ''}>
will render an empty class attribute if the variable answer has the value 42, whereas
<div class="#{#answer != 42 ? 'error' : null}>
will render no class attribute at all in this case.
Loops
For the repeated display of elements (for example for tables and lists) Catapult offers the attribute foreach in the core namespace http://www.catapultframework.org/xml/core. The value of the foreach attribute is typically an expression of the form
iterable.iterator('loopvar' [, 'statusvar'])
- The subexpression iterable must evaluate to an array, a collection, or a map.
- The string loopvar defines the name of the loop variable.
- The optional string statusvar defines the name of an additional status variable for the the loop.
The element that holds the foreach attribute will be repeatedly rendered for each item in iterable. Within the element the current item is stored in the loop variable.
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://www.catapultframework.org/xml/core">
...
<ul>
<li c:foreach="#{#user.betSlip.matches.iterator('match')}">
#{#match.goalsA} : #{#match.goalsB}
</li>
</ul>
...
This example renders a list of all guessed match results of the user.
<table>
<tr c:foreach="#{header.iterator('h')}">
<td>#{#h.key}</td>
<td>#{#h.value}</td>
</tr>
</table>
This example renders a table containing the HTTP header values of the current request. Since header is a map, the loop variable h contains a java.util.Map.Entry value in each iteration. By accessing its properties key and value we can display the map contents.
By using the status variable we can access additional properties of the loop:
indexreturns the index (number) of the current iteration, 0 based.countreturns the index (number) of the current iteration, 1 based. This meanscount = index + 1.firstreturnstruefor the first iteration andfalseotherwise.lastreturnstruefor the last iteration andfalseotherwise.evenreturnstruefor those iteraton whereindexis even.oddreturnstruefor those iteraton whereindexis odd.cycle(arg1, arg2, ...)returns one element after the other from the cycle during the iteration.
The following example illustrates the behavior of the cycle function:
<table>
<tr c:foreach="#{header.iterator('h', 'status')}"
class="#{#status.cycle('red', 'green', 'blue')}">
<td>#{#h.key}</td>
<td>#{#h.value}</td>
</tr>
</table>
Here the class values red, green, and blue will be used consecutively.
Additional blocks
Since both constructs for conditions (c:if) and loops (c:foreach) are XML attributes, they always require a single XML element that carries the control construct. For those cases when you don’t have a single element, the c:block element (also in Catapult’s core namespace http://www.catapultframework.org/xml/core) comes handy. The element itself won’t be rendered, its only purpose is to group other content for c:if or c:foreach as well as for defining local variables (see below).
The following example alters the HTTP headers example from above and outputs a definition list instead of a table:
<dl>
<c:block c:foreach="#{header.iterator('h')}">
<dt>#{#h.key}</dt>
<dd>#{#h.value}</dd>
</c:block>
</dl>
The c:block is necessary since the pair of dt and dd elements has to be repeated.
Default parameters
As we have seen above in the templating chapter, variables may be passed to templates by using an attribute in the template element. Catapult requires all variables to be assigned, thus it is not allowed to use a variable in a template without an assignment. So for example a typo in a variable name will be reported as an error during the rendering of the page.
Note: this has changed in Catapult 0.99. In versions prior to 0.99 referencing an unassigned variable simply evaluates to null. By setting the Catapult property catapult.renderer.ignore-undeclared-variables to true the previous behaviour can be reestablished.
However, to prevent passing all variable values via attributes, Catapult provides the http://www.catapultframework.org/xml/default-param namespace for defining default values for parameters. A default value of a parameter will be defined by using an attribute with the name of the parameter in the default-param namespace like this:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:d="http://www.catapultframework.org/xml/default-param"
d:title="Hello, world!">
<head>
<title>#{#title}</title>
</head>
...
</html>
The title variable will have the value “Hello, world!” unless it is passed explicitely to the template.
Local variables
Often it is useful to assign a complex SpEL expression to a local variable that will be used several times. Catapult enables element scoped variables by using the param namespace http://www.catapultframework.org/xml/param. Usually this namespace will be bound to the prefix p. Any attribute in the param namespace establishes a variable declaration. This variable can be used in the contents of the element.
Here is an example
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://www.catapultframework.org/xml/core"
xmlns:p="http://www.catapultframework.org/xml/param">
...
<div p:valid="#{#foo.hasValues() and (#bar.prop1 gt 100 or #bar.prop2 lt 50)}">
<span c:if="#{#valid}">The data is valid</span>
<span c:if="#{!#valid}">The data is not valid</span>
</div>
</html>
(TODO: useful example)
While the order of attributes in XML is in general undefined (i.e. the order that is used in the input XML isn’t necessarily used in the output XML), Catapult will evaluate attributes in the core and param namespaces before any other attributes. The order is as follows:
d:*(i.e. all default parameter declarations)c:foreachp:*(i.e. all local variable declarations)c:if- all other attributes
This means, attributes that declare variables (c:foreach and p:*) will be evaluated before conditions (c:if). The following example snippets are legal attribute combinations:
<foo c:foreach="#{#collection.iterator('x')}"
p:double="#{2 * #x}"
bar="#{#double}"> ...
<foo c:foreach="#{#collection.iterator('x')}"
c:if="#{#x != 42}"> ...
<foo p:name="#{#car?.owner?.name}"
c:if="#{#name != null}"> ...
However, the following examples are illegal and will not work:
<foo c:if="{#collection != null}"
c:foreach="#{#collection.iterator('i')}"> ...
<-- #collection.iterator gets called even if collection is null -->
<foo p:matches="#{#user.betSlip.matches}"
c:foreach="#{#matches.iterator('m')}"> ...
<-- matches is null when c:foreach is evaluated -->
<foo c:if="#{#user != null}"
p:matches="#{#user.betSlip.matches}"> ...
<-- #user.betSlip will be evaluated even if user is null -->
The solution to this kind of problems is usually to move the conflicting attributes to the next outer (parent) or inner (child) element. If you don’t find proper elements you can always add a c:block. The first of the illegal example above can be fixed by using
<c:block c:if="{#collection != null}">
<foo c:foreach="#{#collection.iterator('i')}"> ...
Note: For multiple attributes in the param namespace the evaluation order is still undefined. This means: never declare multiple variables at once that refer each other. The behavior of the following declarations is undefined:
<div p:foo="#{13}" p:bar="#{#foo + 42}">
If you are lucky, bar will have the value 55 afterwards. But it might as well be that you are seeing an error because foo is null when p:bar is evaluated. The solution here is as well to put the variable declarations to different elements (for example to an additional c:block).
Comments
Catapult supports two kinds of comments within HTML resp. XML templates.
A simple comment of the form <-- comment contents ... --> will be evaluated and rendered. Thus the resulting page will contain the comment with all contained expressions evaluated. So this kind of comment is normally not appropriate for commenting out code, because the code will be rendered within the comment and invalid EL expressions might cause errors.
In contrast, Catapult comments of the form <--% comment contents ... %--> will be completely ignored by Catapult (note the additional percent characters next to the comment delimiters). EL expressions commented out this way won’t be evaluated by Catapult and won’t be seen by users in the resulting HTML source.
