Magnolia 4.5 reached end of life on June 30, 2016. This branch is no longer supported, see End-of-life policy.
This document talks about templating in Magnolia 4.5 and later.
A template script renders output. Typically that output is HTML. The script is written in a templating language such as FreeMarker or JSP. The script instructs the renderer where to place the content on the page and contains placeholders for content attributes such as headings and images.
Magnolia provides renderers for Freemarker and JSP out of the box. You can choose the language you prefer. You can even mix languages on the same site, using Freemarker in some templates and JSP in others.
At Magnolia we prefer Freemarker for its flexibility, cleaner syntax and better error reporting but also because it does not depend on the file system. Templates do not have to be extracted to the file system. This means you can store them in the repository and access like any other resources, apply version control if you wish, and add custom properties and metadata.
Example: Render the page title, or render the name if no title exists.
<h3>${content.title!content.@name}</h3>
Here are some of the benefits that Freemarker offers:
[if]
, [else]
and [list]
.JSP stands for JavaServer Pages. It is an extension of Java Servlet technology for combining Java server-side programs and HTML.
Example: Render the page title or the name if no title exists.
<h3> <c:choose> <c:when test="${not empty content.title}">${content.title}</c:when> <c:otherwise>${content['@name']}</c:otherwise> </c:choose> </h3>
You can also use another templating language. Some sites use Apache Velocity, for example. If a renderer for your language is available, you can most likely incorporate it into Magnolia. This allows you to use a templating language that you are already familiar with.
FreemarkerRenderer
and
JspRenderer
are configured in /modules/rendering/renderers/freemarker
and /jsp
.
The STK defines its own renderer
STKRenderer
configured in /modules/standard-templating kit/rendering/renderers/stk
.
The renderType
property in page, area and component definitions defines the template renderer to use. For STK templates the type is stk
.
Freemarker exceptions render differently on the author and public instances. On author, Freemarker exceptions show the stack trace in a yellow block with red text, and on public in preview mode errors are hidden and logged.
This behavior is controlled by
ModeDependentTemplateExceptionHandler
registered in configuration in /server/rendering/freemarker/templateExceptionHandler
. You can extend this class to fine-grain behavior.
The system can load Freemarker scripts from three places in this order:
/<CATALINA_HOME>/webapps/<contextPath>/templates
folder.templates
workspace. The template needs to be enabled to be considered.Freemarker template loaders are configured in /server/rendering/freemarker/templateLoaders/jcr
and /webapp
:
You can also write your own custom template loader if needed.
JSP scripts can only reside on the file system because they require pre-compiling by the server before they can be rendered. JSP scripts are extracted onto the file system during module installation or update
In order to edit template scripts in the repository, install them into the templates
workspace. This is useful for prototyping or developing but it only works with FreeMarker scripts. The scripts become editable in Templating Kit > Templates.
Add a TemplatesInstallTask into your module version handler. Here is an example from the Standard Templating Kit module (Git):
import info.magnolia.module.inplacetemplating.setup.TemplatesInstallTask; // Install scripts when the module is installed for the first time. @Override protected List<Task> getExtraInstallTasks(InstallContext ctx) { final List<Task> tasks = new ArrayList<Task>(); tasks.addAll(super.getExtraInstallTasks(ctx)); tasks.add(new TemplatesInstallTask("/templating-kit/.*\\.ftl", true));@Override // Reinstall scripts when the module is updated. @Override protected List<Task> getDefaultUpdateTasks(Version forVersion) { final List<Task> tasks = new ArrayList<Task>(); tasks.addAll(super.getDefaultUpdateTasks(forVersion)); tasks.add(new TemplatesInstallTask("/templating-kit/.*\\.ftl", true)); return tasks; }
Editing template scripts in the repository is not maintainable in the long run because the changes don't make their way into the module. You can use this approach for evaluation and prototyping but don't do it in a production environment. We strongly recommend that you store the scripts in a version control system and package them into a project module.
To edit a template script in the repository:
main.ftl
(Git) script is in a /templating-kit/pages
folder inside the module JAR. Creating the matching folders in the repository allows the repository script override the classpath script.To install JSP scripts on to the file system put them in /mgnl-files/templates/<module-name>
in your module. Magnolia deploys them to /<CATALINA_HOME>/webapps/<contextPath>/templates/
on the file system during module install and update.
STK template scripts are in the classpath of the webapp. Copies are editable in-place in Templating Kit > Templates in AdminCentral. STK scripts are written in FreeMarker.
The scripts are stored in child folders of the /templating-kit
folder:
components
contains component templates sorted by component type.pages
contains the main
page script and area scripts in subfolders.This structure follows the template prototype structure. Area templates referenced in the template prototype are stored in the global
folder.
main
is the the master page script (Git). It is a good starting point to understand the system. It initializes the page editor on the author instance using the @cms.init
directive. It also contains the logic to set the body class and body ID.
The main script calls areas to render themselves using the @cms.area
directive. It renders the navigation using the #include
directive. The main
script wraps the areas in three div
elements that creates a flexible page grid.
See STK areas for descriptions of STK area scripts.
Key templating features are available as directives in the Magnolia tag library. Directives are quick to type but can render complex output.
Here are the most useful Freemarker directives with sample code:
Common operators ( &&, ||, !, ==, !=, >, <, >=, <=
) are supported
Boolean test
[#if content.header?has_content] <h1>${content.header}</h1> [#else] DO_SOMETHING_ELSE [/#if]
[#if content.imageLocation == "top"] … [/#if]
[#if content.date?has_content] ${content.date?time?string.short} [#elseif content.endDate?has_content] ${content.endDate?time?string.short} [#else] No date is set! [/#if]
Can iterate over any collection that extends a Java collection.
[#list model.getSomeList() as elem] <li>${elem.title!}</li> [/#list]
Assign allows you to define variables. Any object except null can be passed to a variable.
[#assign title = content.title!content.@name] [#assign hasDate = content.date?has_content] [#assign dateShort = content.date?time?string.short] [#assign events = model.events] [#assign stringgy = "Some direct string data"]
The include directive includes a Freemarker template script. .
[#include "/templating-kit/templates/content/myScript.ftl"]
Macros allow you to reuse snippets of Freemarker code. See Templating Kit > Templates /templating-kit/components/macros
for examples used in the STK.
[#macro test foo bar="Bar" baaz=-1] Test text, and the params: ${foo}, ${bar}, ${baaz} [/#macro] [@test foo="a" bar="b" baaz=5*5-2/] [@test foo="a" bar="b"/] [@test foo="a" baaz=5*5-2/] [@test foo="a"/]
Magnolia provides the following custom directives:
cms:init
renders all the "top matter" in a page HTML and the main bar.cms:area
renders an area.cms:component
renders a componentFor Freemarker, these directives are implemented by the Directives class. This class is configured in /modules/rendering/renderers/freemarker/contextAttributes/cms/componentClass
.
For JSP, the directives are provided by the Templating JSP module.
The directive syntax differs slightly depending on the templating language. Freemarker directives start with the # character in the case of standard directives and with the @ character for custom directives. All directives in the Magnolia cms
tag library start with @. What follows is the tag library name such as cms
, a dot character, the name of the macro, and any parameters. In JSP the limiting characters are different.
Syntax:
[@<tag library>.<macro> <parameter>=<value> /]
Freemarker example: Render a component
[@cms.component content=component /]
JSP example: Render a component
<cms:component content="${component}"/>
The cms.init
directive initializes the page editor and renders the top bar. The
InitElement
class embeds the JavaScript and CSS needed to edit pages on the author instance. The output goes in the <head>
element in the page HTML.
Example:
[@cms.init /]
The "Currently not collaborating" message you see in the image above is provided by the Soft Locking module. It is an Enterprise Edition module. Community Edition users do not see the message.
The cms:area
directive (
AreaDirective
) renders an area and any components inside it. The area toolbar allows editors to add components inside the area. Available components are configured in the area definition.
Example:
[@cms.area name="main"/]
The directive references an area by its name. The area name is the content node that contains the area definition such as main
, footer
or stage
.
The result on the page is an area bar, a start marker, an optional placeholder box, and an end marker. The value of the title
property in the area definition is rendered on the bar. When an editor clicks the placeholder they can add components inside the area.
If the area definition contains a templateScript
property then the referenced script renders the area. If no script is given then the following default scripts are used instead:
Area type single
[@cms.component content=component /]
Area type list
[#list components as component] [@cms.component content=component /] [/#list]
The cms:component
directive (
ComponentDirective
) renders a component. The content
attribute defines what content the component edits. This tag is commonly used inside the list
directive to loop through the components in a collection.
The content to render, and possibly edit in case of an editable component, is passed in the content
attribute. On the author instance the directive renders a component toolbar. The value of the title
property in the component definition is rendered on the bar.
Attribute | Description | Default Value |
---|---|---|
editable | Defines whether edit icons should be displayed. Mainly useful if content is inherited. | cmsfn.isFromCurrentPage() |
template | Name of the component definition to use | The template defined in the node. |
Example:
[#list components as component ] [@cms.component content=component /] [/#list]
The following attributes can be passed with any directive. They define which content the element created by the directive should work on.
Attribute | Description | Default value |
---|---|---|
content | A Node or ContentMap. | |
workspace | Workspace used if path is defined. | Same as of the current content |
path | Path in the workspace. |
The content
attribute tells a script which content node it should operate on. Scripts typically operate on the "current" content node. For a page-level script the current node is the page, for an area-level script the current node is the area, and for a component-level script the current node is the component. However, there are cases where you want the script to operate on a different content node. This is where the content
attribute comes handy.
For example, the intro area has no content of its own. It doesn't contain any components either since it is of type noComponent
. The area operates on page content instead. It edits and renders the page title and abstract. We achieve this by using the content attribute.
In the main.ftl
script we tell the main area "You should operate on the current content node, which is a page because I am a page-level script".
<div id="wrapper-3"> [@cms.area name="platform"/] [@cms.area name="main" content=content/] [@cms.area name="extras"/] </div>
In the mainArea
area script we again pass the same instruction down to the intro area: "You should operate on the current content node which is (still) the page".
<div id="main" role="main"> [@cms.area name="breadcrumb" content=content/] [@cms.area name="intro" content=content/] [@cms.area name="opener"/] [@cms.area name="content"/] </div><!-- end main -->
Now the intro area edits page content. Although the intro area resides inside the main area div element on the page, the title and the abstract really belong to the page. They are the page's properties, not the area's. So it makes sense to store those properties under the page node in the content structure.
The workspace
attribute tells the directive which workspace of the magnolia
JCR repository the content resides in. This is almost always the website
workspace and defaults to website
automatically if the current content resides in the website
workspace.
Here is an example how directives are rendered on the page.
main
script contains a cms.init
directive which creates the toolbar at the top of the page.cms.area
directive calls an area to be rendered. The directive identifies the area by name, in this case extras
. If the area has child areas you need a separate script which calls the children to be rendered. However, if the area contains only components you don't need an area script.cms.area
directive. This area does not have child areas, only components.You can add your own directives. They make Java methods and functions in your own classes available to template scripts:
WEB-INF/classes
folder of your Magnolia web application./modules/rendering/renderers/freemarker/contextAttributes
./contextAttributes
, create a content node such as myClass
. Name it after the purpose of your class.myClass
, create two data nodes:componentClass
and set its value to the fully-qualified name of the class you placed in WEB-INF/classes
.name
and set the value to myClass
.This allows you to access all the static methods in myClass
from templates using the ${myClass.myMethod()
} Freemarker syntax.
TemplatingFunctions
includes useful methods that you can use in your templates. The methods are exposed as cmsfn
. The decode
method that removes escaping of HTML on properties is is an example and the snippet below shows its use in the stkTextImage
component script.
[#if content.text?has_content] ${cmsfn.decode(content).text} [/#if]
STKTemplatingFunctions
makes additional methods available for use in STK templates. The methods are exposed as stkfn
. The snippet below from the promos
component script contains two examples: abbreviateString
and getAssetVariation
.
[#assign text = stkfn.abbreviateString(text, 80)] [#assign image = model.image!] [#if image?has_content && !hideTeaserImage] [#assign imageLink = stkfn.getAssetVariation(image, "promo").link]
The templating functions classes are configured for each renderer in /<module>/rendering/renderers/<renderer>/contextAttributes/<tag library>
. Note that the <tag library>
content node and the value of the name
data node matches the syntax used to expose the methods in scripts.
FreeMarker provides a powerful set of built-ins. These are used for basic manipulation of data and no Java code is necessary. Built-ins are used with a preceding ?
character. For example ?exists
checks if a value/object exists and ?has_content
checks if a value/object is empty and exists.
Most Java String are implemented and can be used directly in Freemarker. Examples: substring
, uncap_first
, capitalize
, date, time, datetime
, ends_with
, html
, index_of
, last_index_of
, length
, lower_case
, upper_case
, contains
, replace
, starts_with
, trim
.
String (when used with a boolean value) converts a boolean to a string. You can use it in two ways:
foo?string
converts the boolean to string using the default strings to represent true and false values.foo?string("yes", "no")
returns the first parameter "yes" if the boolean is true, otherwise the second parameter "no".There are various built-ins for dates with formating capabilities. For example:
[#assign microFormatDate = content.date?string("yyyy-MM-dd") + "T" + ontent.date?string("hh:mm:ss")]
There are various expert built-ins. The most commonly used are:
has_content
determines if HTML is rendered to avoid empty HTML tags.
[#if content.image?has_content] <img src="${contextPath + content.image}"> [/#if]
eval
evaluates the passed Freemarker code.
[#assign indexString = ('"'+(ctx.indexString!)+'"')?eval]
These rendering context objects are set in AbstractRenderer and its child classes,
content
: the current content
node.
${content.header!}
model
: The example code below corresponds to getNavigation()
method of the model class.
${model.navigation!}
def
: The current page, area or component definition object.
${def.headingLevel!}
ctx
: See
WebContext
.
${ctx.user.name!}
<img src="${ctx.contextPath}${content.image.@path!}">
state
: See
AggregationState
.
${state.locale!}
Null checks stabilize your templates. Freemarker throws an exception if null is encountered. There are two options:
Use the !
character to provide default values. The content after !
is executed.
This code tries to assign title
from content, if not it falls back to the content node's name
.
<meta name="keywords" content="${content.keywords!content.title!content.@name}" />
You can also specify the value.
[#if content.keywordsEnabled!false] <meta name="keywords" content="${content.keywords!"These are some keywords"}" /> [/#if]
Or use the ?has_content
built-in. The example renders the header
in h1
tags if a value exists.
[#if content.header?has_content] <h1>${content.header}</h1> [/#if]
Here are the most common Freemarker examples for use in your template scripts:
[#-- Accessing content --] The value of "someProperty": ${content.someProperty} Accessing a child node: ${content.childNode} Accessing the child node collection: ${content?children} Accessing the parent node: ${content?parent} [#-- Special content properties --] The content object is an instance of ContentMap and the following attributes are available: The current node name: ${content.@name} The current node path: ${content.@path} The current node id: ${content.@id} The current node depth: ${content.@depth} The current node node type: ${content.@nodeType} [#-- MetaData --] The creation date: ${content.metaData.creationDate} Metadata.modificationDate: ${content.metaData.modificationDate!" This node has never been modified."} [#-- Component definition --] The current component definition: ${def.name} A component definition property: ${def.style} [#-- Context: ctx --] A request parmeter: ${ctx.myParam} The current user name ${ctx.user.name} The current locale ${ctx.locale} [#-- TemplatingFunctions: cmsfn--] Create a link to a page: ${cmsfn.link(content)} Create a binary link: ${cmsfn.link(content.image)} [#-- Status based rendering --] This is ${cmsfn.authorInstance?string('indeed', 'not')} an author instance. This is ${cmsfn.editMode?string('indeed', 'not')} the edit mode. This is ${cmsfn.previewMode?string('indeed', 'not')} the preview mode. [#-- The Model executed before the paragraph rendering: model --] The parent model: {model.parent} The result of the execute method: ${actionResult} [#-- AggregationState: state --] Entry point of the rendering: ${state.mainContent} Current content node: ${state.currentContent}
Useful links to get started with Freemarker:
[#blocks]
of code which can be assimilated to jsp tags.
2 Comments
Nils Breunese
/server/rendering/freemarker/jcr should be /server/rendering/freemarker/templateLoaders/jcr
Ruth Stocks
Thanks Nils! Fixed!