Template Guide For Alfresco 1.4 and 2.0

Document created by resplin Employee on Jun 6, 2015Last modified by robert.hanson on Sep 1, 2016
Version 12Show Document
  • View in full screen mode

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.

 

Viewing Templates in the Web-Client

Templates can be applied to Documents and Space objects by following these steps:

  1. Create a template file (a good start is to copy-and-paste an example from the Examples section below or copy from one from the /web-client/config/templates directory in the Alfresco source code distribution).
  2. Add the template file you created to the "Company Home/Data Dictionary/Presentation Templates" folder in your repository - several examples are provided here as part of the default installed system.
  3. Click the "Preview In Template" action next to a Document or a Space in the main browsing screen.
  4. The screen that appears has a drop-down menu in the top right of the screen, this shows a list of the available templates found the Content Templates folder as above. When you select one it is applied to the current document.

Custom Template Views

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.

Template Processing Servlet

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

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.

 

Models

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.

 

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:

companyhome 
The Company Home template node.
userhome 
Current users Home Space template node.
person 
Node representing the current users Person object.

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:

space 
The current space template node.

If you are accessing the templates through the Document Preview action or through the Template Servlet then the following named objects are also provided:

document 
The current document template node.
template 
The node representing the template itself.
args 
A Map of any URL parameters passed via the Template Content Servlet (only available if the template was executed via the servlet).
<#assign keys = args?keys>
<#list keys as arg>     
   ${arg}
</#list>

session 

Session related information (session.ticket for the authentication ticket)
classification 
Read access to classifications and root categories, see the Classification section.


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.

 

TemplateNode Model API

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:

properties 
a map of the properties of the node. For example userhome.properties.name. Properties may return several different types of object - this depends entirely on the underlying property type in the repository. If the property is multi-value then the result will be a sequence which can be indexed like any other sequence or array. If the result is an unknown or unsupported type then the toString() result is generally used - therefore the result will mostly be a String type.

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}
children 
a sequence (list) of the child nodes. For example mynode.children[0].
assocs 
a map of the associations of the node. Each entry in the map contains a sequence of the Node objects on the end of the association. For examplemynode.assocs["cm:translations"][0].
aspects 
a sequence of the aspects (as QName strings) applied to the node.
isContainer 
true if the node is a folder node, false otherwise.
isDocument 
true if the node is a content node, false otherwise.
isCategory 
true if the node is a category node, false otherwise.
content 
returns the content of the node as a string.
url 
the url to the content stream for this node.
displayPath 
the display path to this node.
icon16 
the small icon image for this node.
icon32 
the large icon image for this node.
mimetype 
the mimetype encoding for content attached to this node.
size 
the size in bytes of content attached to this node.
isLocked 
true if the node is locked, false otherwise.
id 
GUID for the node.
nodeRef 
NodeRef string for the node.
name 
shortcut access to the name property.
type 
fully qualified QName type of the node.
parent 
the parent node, can be null if this is the root node.
permissions 
sequence of the permissions explicitly applied to this node. Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION for example ALLOWED;kevinr;Consumer so can be easily tokenized on the ';' character.
inheritsPermissions 
true if the node inherits its parent node permissions, false if the permissions are applied specifically.

 

Extended TemplateNode Model API

childrenByXPath 
returns a map capable of executing an XPath query to find child nodes e.g. companyhome.childrenByXPath["*[@cm:name='Data Dictionary']/*"] The map is executing an XPath search against the current node, it returns a sequence of the nodes as results of the query.
childByNamePath 
returns a map capable of returning a single child node found by name path e.g. companyhome.childByNamePath["Data Dictionary/Content Templates"] Under the covers, this method is building a XPath and executing a search against the "cm:name" attribute on children of the current node. This method allows you to find a specific child node if you know its 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.

childrenBySavedSearch 
returns a map capable of executing a search based on a previously Saved Search object. It returns a sequence of child nodes which represent the objects from the results of the search e.g.

companyhome.childrenBySavedSearch["workspace://SpacesStore/92005879-996a-11da-bfbc-f7140598adfe"] The value specified must be a NodeRef to an existing Saved Search object.

childrenByLuceneSearch 
returns a map capable of executing a search against the entire repository based on a Lucene search string. It returns a sequence of nodes which represent the objects from the results of the search e.g. companyhome.childrenByLuceneSearch["TEXT:alfresco* AND TEXT:tutorial*"] The value can be any valid Lucene search string supported by Alfresco, note that you may need to escape Lucene specific characters, see the Examples section below. Note that the entire repository is searched, the current node is only used as an access point to retrieve the search object.
nodeByReference 
returns a map capable of executing a search for a single node by NodeRef reference. This method allows you to find a node if you have the full NodeRef string or NodeRef object, see the Examples section below.
qnamePath 
returns the QName based Path to the node. This is useful for building Lucene PATH: style queries that constrain to a path location.

 

Version History

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.

versionHistory 
returns a sequence of version history record nodes for a versioned TemplateNode.

Each version history record node has the following API:

id 
GUID for the node.
nodeRef 
NodeRef string for the node.
name 
name property of the node version record.
type 
fully qualified QName type of the node.
createdDate 
created date of the version.
creator 
creator of the version.
versionLabel 
version label of the verson record.
isMajorVersion 
boolean true if this was a major version.
description 
version history description.
url 
url to the content stream for the frozen content state.

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.

 

Classification

Read access to classifications and root categories.

classification.getRootCategories(String aspectQName) 
get the root categories for a classification.
classification.getAllCategoryNodes(String aspectQName) 
get the category nodes for a classification.
classification.allClassificationAspects 
get the qnames of all classification aspects.

The following extended node API methods are provided to work with Category node objects.

categoryMembers 
get all members of a category.
subCategories 
get all sub-categories of a category.
membersAndSubCategories 
get all sub-categories and members of a category.
immediateCategoryMembers 
get all immediate members of a category.
immediateSubCategories 
get all immediate sub-categories of a category.
immediateMembersAndSubCategories 
get all immediate sub-categories of a category.

 

XML Content Processing Model API

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.

xmlNodeModel 
returns the XML DOM model object for the content of the node. If the node content is valid XML, and the XML can be parsed ok, then this method returns the root of the DOM for this node. The DOM can be walked and processed using the syntax as per the FreeMarker XML Processing Guide. Also see the Examples section below.

 

Custom Models

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.

 

Methods

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.

 

Current date

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)}

 

JSP Page

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>

Examples

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>

Template4.png


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}

 

How is Templating integrated into Alfresco?

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);
}

Template Service Configuration

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);
}

Outcomes