IMPORTANT: This document details the Templating API and Templating Services for Alfresco 2.0 or 1.4. For more recent versions then please see this document: Template Guide. Note that this page will no longer be maintained or updated except to correct any mistakes.
A template is a document that can be applied to an object or objects (e.g. one or more documents from the repository) to produce another document. An example is aFreeMarker or XSLT template file.
Template + data model = output
The template is written in a specific templating language and the data model consists of objects which are available to the template. Generally the objects would consist of say the current document or folder.
Think of the template engine as a mechanism for rendering an output page based on a defined template page and a data model.
The Alfresco pluggable template system allows for multiple integrated template engines to be used within Alfresco, made available through a repository public service API (TemplateService) and through a web-client UITemplate component and associated pages.
The service is designed to allow the addition of multiple template engines, via Spring XML service configuration and the implementation of a simple interface by the developer.
Templates consist of a simple template text file that can be stored either on the ClassPath or in a Repository. The template files are executed against the default or a custom Model and the output is directed to an output stream such as an Alfresco document node or to a web-client client page via a JSF component.
A JSP page can contain any number of UI Template components, and therefore display any number of different templates and associated models.
Templates can be applied to Documents and Space objects by following these steps:
It is possible to apply a template as a "custom view" for a Space in the web-client UI. A Custom View can be setup using the View Details action screen for a Space. Select the template you wish to be used as a Custom View from the View Details page. This enables the Custom View view mode in the main browsing screen for that Space. Selecting that view mode will execute the template against the space.
A special servlet is provided that allows the output of a template+model to be returned directly on the response. The guide to URL Addressability contains examples on how to access the servlet.
Template files can currently be stored either on the ClassPath (for example in alfresco/config/templates) or in a repository store. The location of the template file is a mandatory attribute to the Template JSF component.
The individual files will be specific to a particular template engine, by default FreeMarker template files will be used. FreeMarker documentation on the FreeMarker template language reference and the FreeMarker page designer manual.
The template engine is not tied to any output file format, templates can output entire HTML files, snippets of HTML, XML or any other format as desired.
Examples showing FreeMarker templating language and how to bind against the Alfresco data model are shown below.
A model is an object or generally a hierarchy of Java Bean objects from which a template file retrieves values for output. The page developer can either make use of the default model provided by the JSF Template component and template servlet or they can define a custom model which will be merged into the default model.
The default model provides a number of root objects by default. There are several "TemplateNode" objects that wrap common Alfresco Node objects to provide an rich OO layer suitable for scripting usage. If you are accessing the templates through the web-client UITemplate component, then the following named objects are provided by default at the root of the model:
If you are accessing the templates through the Space Preview action, the Template Servlet or through the Space Dashboard then the following named object is also provided:
If you are accessing the templates through the Document Preview action or through the Template Servlet then the following named objects are also provided:
<#assign keys = args?keys>
<#list keys as arg>
${arg}
</#list>
session
The default model objects can be accessed directly from the root of a model thus:
userhome.properties.name
The node children/association/aspect etc. model is built dynamically as it is required. E.g. you can write statements such as:
userhome.children[1].children[0].children[2].name
Important Note: It should be noted that the FreeMarker template engine is very strict on the access of empty or null values! Unlike many templating or scripting languages, that display empty values or assume FALSE as a default value for a missing property, the FreeMarker engine will instead throw an exception and abort the rendering of the template. Therefore to counter this, most of the TemplateNode API calls provided by the default model that return Maps or Sequences of items will return empty Maps and Lists instead of null. Also if a null value may be returned by a call (e.g. from accessing a Map to find a value by name), you should use the FreeMarker exists method syntax to check for null first, thus:
<#if mynode.assocs["cm:translations"]?exists>
${mynode.assocs["cm:translations"][0].content}
</#if>
This checks for the existance of a translation association before attempting to access it's associated Sequence.
These objects, and any child node objects are called "Template Node" objects they provide access to the common Alfresco concepts such as properties, aspects and associations. The following API is available:
Date and Boolean property values should be handled carefully. Use of the FreeMarker methods is_date and is_boolean can be used to check if the page developer is unsure of the property value type. These values can then be formatted as appropriate - see the Examples below.
One special feature is that if the type of property is a NodeRef object, then the templating system will automatically convert the property type into another TemplateNode object. This means the page writer can continue to walk the object hierarchy for that node e.g. If a document node has a NodeRef property called "locale" you could execute the following to retrieve the name of the node the property references:
Locale: ${document.properties.locale.properties.name}
Note that the above API calls use the node they are executed against as the current context for the query e.g. if you have a folder node called "myfolder" and you execute the call: myfolder.childByNamePath["MyChildFolder"] then the search will try to find a folder called "MyChildFolder" as the child of the myfolder node.
companyhome.childrenBySavedSearch["workspace://SpacesStore/92005879-996a-11da-bfbc-f7140598adfe"] The value specified must be a NodeRef to an existing Saved Search object.
This API is available in Alfresco 2.0!
Meta-data and content for previous version of a versioned document node can be obtained through the version history API.
Each version history record node has the following API:
In addition the properties and aspect API as described above for TemplateNode is available which returns the frozen history state of the properties and aspects for the node version record.
Read access to classifications and root categories.
The following extended node API methods are provided to work with Category node objects.
The FreeMarker language supports XML DOM processing using either DOM functional or macro style declarative operations. The Alfresco TemplateNode API has been extended to provide access to the FreeMarker DOM model objects.
Custom models can be bound into the JSF Template component for use by your templates. Custom models should be provided as a java.util.Map implementation and will be automatically merged into the default model. This means all root objects in your custom model will be accessable at the same level as the default model root objects.
Custom models can contain any object valid for your particular choice of template engine. By default the FreeMarker supports most standard Java types and Collections, see the FreeMarker data model reference for more info.
If you wish to expose Node objects in your model, it is recommended that you follow the same pattern as used by the default model and create classes of the type:
org.alfresco.service.cmr.repository.TemplateNode
As these objects automatically provide the API described above and dynamic access to child nodes etc. Any other objects can be added to your custom model as you see fit.
To use a custom model and use it with the UI Template component, create your Map in a JSF Bean and bind a method returning it in the normal way to your template component on a page:
<r:template template="alfresco/templates/example.ftl" model="#{MyBean.templateModel}" />
Example Bean code to return a model:
/**
* Returns a model for use by a template on the Document Details page.
*
* @return model containing current document and current space info.
*/
public Map getTemplateModel()
{
HashMap model = new HashMap(1, 1.0f);
TemplateNode documentNode = new TemplateNode(getDocument().getNodeRef(), this.nodeService);
model.put("document", documentNode);
return model;
}
As mentioned above, the model will be merged into the default model.
Custom methods can be added to the FreeMarker language for use on template pages. The default model provides the following additional methods:
hasAspect(TemplateNode, String) - will return the integer value 1 if the TemplateNode supplied has the aspect with the supplied QName String, else the integer value 0 will be returned.
dateCompare(DateA, DateB) - Compare two dates to see if they differ. Returns 1 if DateA if greater than DateB, else returns 0.
dateCompare(DateA, DateB, Number) - Compare two dates to see if they differ by the specified number of milliseconds. Returns 1 if DateA is greater than DateB by at least the Number value in milliseconds, else returns 0.
message(String) - will return the I18N message string (resolved for current user Locale setting) for the specified String message ID.
More information on adding your own custom methods: [FreeMarker Custom Methods]. It should be noted that the return values for all custom methods are rather limited. It is only possible to return String, number or date object. This is why the custom method described above does not return a boolean value as you might expect.
As Freemarker is a templating language there is no such variable as "today". So the current date (as a new Date() Java object) can be accessed in a template as the "date" object in the root of the model. For example:
<#assign datetimeformat="EEE, dd MMM yyyy HH:mm:ss zzz"> ${date?string(datetimeformat)}
Minimum JSP code required to display a template using the JSF Template component:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %>
<html>
<body>
<f:view>
<h:form>
<r:template template="alfresco/templates/userhome_docs.ftl" />
</h:form>
</f:view>
</body>
</html>
All examples below are complete templates and use the FreeMarker template language and using the default Alfresco Template Model. Additional examples from the community can be found here http://wiki.alfresco.com/wiki/Example_Presentation_Templates.
Display the Company Home Space name:
<b>Company Home Space:</b> ${companyhome.properties.name}
The number of nodes below the users Home Space:
<b>My Home Space children count:</b> ${userhome.children?size}
Whether a node called document is currently locked:
<b>Locked:</b> <#if document.isLocked>Yes<#else>No</#if>
Iterate over all properties for a node called document, and render the values as appropriate for the data-types returned:
<table>
<#-- Get a list of all the property names for the document -->
<#assign props = document.properties?keys>
<#list props as t>
<#-- If the property exists -->
<#if document.properties[t]?exists>
<#-- If it is a date, format it accordingly-->
<#if document.properties[t]?is_date>
<tr><td>${t} = ${document.properties[t]?date}</td></tr>
<#-- If it is a boolean, format it accordingly-->
<#elseif document.properties[t]?is_boolean>
<tr><td>${t} = ${document.properties[t]?string("yes", "no")}</td></tr>
<#-- Otherwise treat it as a string -->
<#else>
<tr><td>${t} = ${document.properties[t]}</td></tr>
</#if>
</#if>
</#list>
</table>
Renders a tabular list of all the documents in the users Home Space, and display the content of any plain text documents directly inline, and any JPG documents are shown as inline thumbnails:
<table>
<#list userhome.children as child>
<#if child.isDocument>
<tr><td>${child.properties.name}</td></tr>
<#if child.mimetype = "text/plain">
<tr><td style='padding-left:16px'>${child.content}</td></tr>
<#elseif child.mimetype = "image/jpeg">
<tr><td style='padding-left:16px'><img width=100 height=65 src="/alfresco${child.url}"><td></tr>
</#if>
</#if>
</#list>
</table>
Display all the Spaces under the Company Home Space in a table, with the appropriate space icon next to the name, and the number of child spaces is shown in brackets next to the folder name:
<table>
<#list companyhome.children as child>
<#if child.isContainer>
<tr>
<td><img src="/alfresco${child.icon32}"></td>
<td><b>${child.properties.name}</b> (${child.children?size})</td>
</tr>
</#if>
</#list>
</table>
Display information on a TemplateNode in the model called 'document':
<b>Name:</b> ${document.name}<br>
<b>Ref:</b> ${document.nodeRef}<br>
<b>Type:</b> ${document.type}<br>
<b>Content URL:</b> <a href="/alfresco${document.url}">/alfresco${document.url}</a><br>
<b>Locked:</b> <#if document.isLocked>Yes<#else>No</#if><br>
<b>Aspects:</b>
<table>
<#list document.aspects as aspect>
<tr><td>${aspect}</td></tr>
</#list>
</table>
If the document has the 'translatable' aspect applied, walk the 'translations' association and view the content of the node at the end of the association:
<b>Translatable:</b>
<#if document.assocs["cm:translations"]?exists>
Yes
<br>
<table>
<#list document.assocs["cm:translations"] as t>
<tr><td>${t.content}</td></tr>
</#list>
</table>
<#else>
No
</#if>
List all the Template Documents in 'Company Home/Data Dictionary/Content Templates' folder:
<table>
<#list companyhome.childByNamePath["Data Dictionary/Content Templates"].children as child>
<#if child.isDocument>
<tr><td><a href="/alfresco${child.url}" target="new">${child.properties.name}</a></td></tr>
</#if>
</#list>
</table>
List the folders in 'Company Home/Data Dictionary/Space Templates/Software Engineering Project' folder using an XPath query:
<table>
<#list companyhome.childrenByXPath["*[@cm:name='Data Dictionary']/*[@cm:name='Space Templates']/*[@cm:name='Software Engineering Project']/*"] as child>
<#if child.isContainer>
<tr><td><img src="/alfresco${child.icon32}"> ${child.properties.name}</td></tr>
</#if>
</#list>
</table>
Execute a Lucene full-text search and list the resulting documents:
<table>
<#list companyhome.childrenByLuceneSearch["TEXT:alfresco* AND TEXT:tutorial*"] as child>
<tr><td><a href="/alfresco${child.url}" target="new">${child.properties.name}</a></td></tr>
</#list>
</table>
Find a node by its ID Node reference using Lucene, note the escaping needed to satisfy both FreeMarker and Lucene language:
<#list companyhome.childrenByLuceneSearch["ID:workspace\\:\\/\\/SpacesStore\\/e661dccb-ecc0-11da-9974-63f65406985a"] as node>
Found: ${node.id}<br>
</#list>
Process the XML document content of a node called 'document', assuming the node content contains the following XML:
<?xml version="1.0" standalone="yes"?>
<book title="Book Title">
<chapter>
<title>Chapter 1</title>
<para>p1.1</para>
<para>p1.2</para>
<para>p1.3</para>
</chapter>
<chapter>
<title>Chapter 2</title>
<para>p2.1</para>
<para>p2.2</para>
</chapter>
</book>
<#if document.mimetype = "text/xml">
<#assign dom=document.xmlNodeModel>
<h1>${dom.book.@title}</h1>
<#list dom.book.chapter as c>
<h2>${c.title}</2>
</#list>
</#if>
Show version history for the current document (Alfresco 2.0 required), with links to content for the previous versions:
<#if document?exists>
<h3>Document Version History for: ${document.name}</h3>
<table cellspacing=4>
<tr align=left><th>Version</th><th>Name</th><th>Description</th><th>Created Date</th><th>Creator</th></tr>
<#list document.versionHistory as record>
<tr>
<td><a href="/alfresco${record.url}" target="new">${record.versionLabel}</a></td>
<td><a href="/alfresco${record.url}" target="new">${record.name}</a></td>
<td><#if record.description?exists>${record.description}</#if></td>
<td>${record.createdDate?datetime}</td>
<td>${record.creator}</td>
</tr>
</#list>
</table>
<#else>
No document found!
</#if>
Externalized Browse View Example
Different externalized browse view (see screenshot below), this example shows the contents of the repository in a simple HTML view without all the usual Alfresco UI adornments. This could be used as an example on how to build a custom website on top of the Alfresco repository. This is also a neat example of an "interactive" template.
Don't forget to replace the two [TEMPLATE] strings with the reference to this template (/workspace/SpacesStore/<templateID>) the default template will be used otherwise and this will generate an error when no default template is set for this node.
View your template by pointing your browser to: http://localhost:8080/alfresco/template/workspace/SpacesStore/<spaceID>/workspace/SpacesStore/<templateID>
<html>
<head>
<title>View ${space.name} space</title>
</head>
<body>
<img src="/alfresco${space.icon32}" alt="Space">
<ul>
<li>Name: <strong>${space.name}</strong></li>
<li>Type: ${space.type}</li>
<li>Path: ${space.displayPath}</li>
<li>Parent: <a href="/alfresco/template/workspace/SpacesStore/${space.parent.id}[TEMPLATE]" title="Up to ${space.parent.name}">${space.parent.name}</a></p></li>
</ul>
<h1>Spaces</h1>
<ul>
<#list space.children as child>
<#if child.isContainer>
<li><img src="/alfresco${child.icon16}" alt="Space"> <a href="/alfresco/template/workspace/SpacesStore/${child.id}[TEMPLATE]" title="Browse to ${child.properties.name}">${child.properties.name}</a></li>
</#if>
</#list>
</ul>
<h1>Documents</h1>
<ul>
<#list space.children as child>
<#if child.isDocument>
<li>
<a href="/alfresco${child.url}" title="Download this file">${child.properties.name}</a>
</li>
</#if>
</#list>
</ul>
</body>
</html>
Find a node from a hardcoded NodeRef value:
Found: ${companyhome.nodeByReference["workspace://SpacesStore/e661dccb-ecc0-11da-9974-63f65406985a"].id}
The repository service providing templating features is called the Template Service. It is a typical Alfresco repository service accessed via a Spring managed bean with the name of TemplateService. Only repository developers will be interested in accessing the TemplateService directly, those more interested in simply writing scripts themselves should skip this section and jump to TemplateNode Model API.
public interface TemplateService
{
/**
* Process a template against the supplied data model and write to the out.
*
* @param engine Name of the template engine to use
* @param template Template (qualified classpath name or noderef)
* @param model Object model to process template against
*
* @return output of the template process as a String
*/
public String processTemplate(String engine, String template, Object model)
throws TemplateException;
/**
* Process a template against the supplied data model and write to the out.
*
* @param engine Name of the template engine to use
* @param template Template (qualified classpath name or noderef)
* @param model Object model to process template against
* @param out Writer object to send output too
*/
public void processTemplate(String engine, String template, Object model, Writer out)
throws TemplateException;
/**
* Return a TemplateProcessor instance for the specified engine name.
* Note that the processor instance is NOT thread safe!
*
* @param engine Name of the template engine to get or null if not found
*
* @return TemplateProcessor
*/
public TemplateProcessor getTemplateProcessor(String engine);
}
The available template engines can be configured in the Alfresco Spring config file template-services-context.xml:
<bean id="templateService" class="org.alfresco.repo.template.TemplateServiceImpl">
<property name="templateEngines">
<map>
<entry key="freemarker">
<value>freeMarkerProcessor</value>
</entry>
</map>
</property>
<property name="defaultTemplateEngine">
<value>freemarker</value>
</property>
</bean>
The templateEngines configuration section specifies a Map of engine names (by which the engine will be identified by the JSF Template component and Template Service) to a fully qualified Java classname or to another Spring configured bean. The supplied classname or Spring bean must implement the TemplateProcessorinterface. By default the FreeMarker Template Engine is available and will be used as the default engine if none is specified during calls to the template services.
Other engines can be added by developers by adding a new entry key+value to the config file, integrating the appropriate libraries and developing a class that implements the following generic interface used by the pluggable template component:
package org.alfresco.service.cmr.repository;
/**
* Interface to be implemented by template engine wrapper classes. The developer is responsible
* for interfacing to an appropriate template engine, using the supplied data model as input to
* the template and directing the output to the Writer stream.
*/
public interface TemplateProcessor
{
/**
* Process a template against the supplied data model and write to the out.
*
* @param template Template name/path
* @param model Object model to process template against
* @param out Writer object to send output too
*/
public void process(String template, Object model, Writer out);
}