Render Velocity Templates
You can render Velocity templates using controllers or pipelines. If you want to share functionality between new and legacy cartridges, we recommend adding your rendering functionality in a utility script that can be used from either controllers or pipeline script nodes. We encourage you to use a controller, even if you intend to overwrite existing functionality that is currently in a pipeline.
Script Rendering Examples
Render the Velocity template using the
dw.Template.Velocity.render
method. If you use a B2C Commerce script to
render a template, you can call the script from either pipelines or controllers.
Hello World - Inline Velocity in a Script File
The following
example renders an inline Velocity template using the dw.Template.Velocity.render
class in a script module file. This code can be included directly in a controller
or required as a script module from a controller or pipeline script
node.
var velocity = require('dw/template/Velocity');
velocity.render("Hello $message", {message : 'World'});
Hello World - Rendering a Template Stored in the Dynamic WebDav Directory
The second example shows a similar 'hello world' example that relies on the Velocity template being located in the file system. You can supply the file name directly: var velocity = require('dw/template/Velocity');
// assume template source to be 'Hello $message'
velocity.renderFile('hello-world-1.vs', {message : 'World'});
You can also look up the file manually, which allows you to check whether the file exists.
var velocity = require('dw/template/Velocity');
// assume template source to be 'Hello $message'
var template = new File(File.getRootDirectory(File.DYNAMIC), 'hello-world-1.vs');
velocity.renderFile(template, {message : 'World'});
Velocity Hello World - Callbacks
You can pass any objects that are used by the Velocity template
during rendering in a JavaScript Map as a parameter of the rendering method. These include,
but aren't limited to, B2C Commerce Script API objects. The engine also supports invoking
methods on those objects. The following example passes the B2C Commerce Script API
URLUtils
object to render a B2C Commerce product URL within a Velocity
template. In the example, this is passed as string literal for better readability.
var File = require("dw/io/File");
var velocity = require('dw/template/Velocity');
var writer = new dw.io.StringWriter();
velocity.render("Hello $message", {message : 'World'}, writer );
var global.html = writer.toString();
Calling URLUtils in Templates
var velocity = require('dw/template/Velocity');
var urlUtil = require('dw/web/URLUtils');
// this renders something like 'http://.../Product-Show?cgid=1234'
velocity.render("$url.abs('Product-Show', 'cgid', '1234')", {url : urlUtil});
// this renders something similar with the pid taken from the current request
velocity.render("$url.abs('Product-Show', 'cgid', $request.CurrentHttpParameterMap.pid.stringValue)", {
url : urlUtil,
request : request
});
Localizing Text
Add the Resource class as a parameter to localize text from a resource bundle.
var web = require("dw/web");
var velocity = require('dw/template/Velocity');
var res = require('dw.web.Resource'); //include to localize resource messages
// render some text taken from a resource bundle
velocity.render("Hello $res.msg('test.key1','test')"), {'res' : Resource});
It
is possible to deploy text resource bundles alongside the Velocity templates in the
dynamic
file location on WebDAV in a resources
directory. Just like Velocity templates, they follow the content lifecycle and are not part
of a code deployment. These text resource bundles are used with the normal
dw.web.Resource class
, as they would be in ISML template. You add
dw.web.Resource
as a parameter to the template and then call it.
The resource bundles in your code cartridges assigned to your site are checked first
and then the dynamic
file location on WebDAV. B2C Commerce uses the first
resource bundle it finds with a specific ID. B2C Commerce searches cartridges in the order
set by the cartridge path and then the dynamic
file location on WebDAV.
This means if there are two resource bundles with the same ID, the resource bundle in the
cartridge wins over the resource bundle in the WebDAV location.
Escaping and VelocityTools
In ISML, all values written to the response are automatically escaped based on the response MIME type, except when the encoding is turned off in ISPRINT. However, Velocity requires the template developer to explicitly escape all dynamic values using the VelocityTools EscapeTool. B2C Commerce supports the EscapeTool in context. To escape a value, add a line similar to the following to your Velocity template:$esc.html($object.myProperty2)
esc
in the example is the EscapeTool.
Supported Velocity Tools
- AlternatorTool
- ComparisonDateTool
- ConversionTool
- DisplayTool
- EscapeTool
- MathTool
- NumberTool
- ResourceTool
- SortTool
- LinkTool
- LoopTool
Adding Remote Includes to Your Velocity Template
You can add
a remote include to your Velocity template using the Velocity.remoteInclude
method.
var system = require("dw/system");
var urlUtil = require('dw/web/URLUtils'); //include to use remote includes to call pipeline or controllers
function execute( pdict : PipelineDictionary ) : Number
{
var velocity = require('dw/template/Velocity');
velocity.render('before $velocity.remoteInclude(\'MyPipeline-Subpipeline\') after', {'velocity' : velocity});
return PIPELET_NEXT;
}
Using caching with Velocity
You can use caching with Velocity by setting the expiration on the response.
var system = require("dw/system");
function execute( args : PipelineDictionary ) : Number
{
var velocity:dw.template.Velocity = require('dw/template/Velocity');
response.setExpires(Date.now()/1000 + 180); //3 minutes
velocity.render("<html><head><title>It Works!</title></head><body><h1 style=\"text-align:center\">It Works! Yay!</h1></body></html>", {});
return PIPELET_NEXT;
}
Wrapping Velocity with ISML
Most of your storefront is
written in ISML, because of the features that ISML offers in terms of consistent styling and
caching (isdecorate
and iscache
tags). ISML also supports
content slots, which let promotions be scheduled and associated with specific customer
groups and previewed for any date. Velocity doesn't support content slots.
In some cases, you might want to wrap your Velocity template in ISML, to take advantage of the features of ISML and the ability to change your layout without affecting code functionality. The following example renders a Velocity segment and includes it in an ISML template.
ContentRender.ds
*
* @input content : dw.content.Content
* @output velocitySegment : String
*/
importPackage( dw.system );
importPackage( dw.io );
importPackage( dw.util );
importPackage( dw.content );
importPackage( dw.template );
function execute( args : PipelineDictionary ) : Number {
var content : Content;
try {
if (args.content != null) {
content = args.content;
}
// Create the Global scope that can be accessed via $VARIABLE in the original template
var global = {
asset : content,
CurrentPageMetaData : request.pageMetaData,
CurrentHttpParameterMap : request.httpParameterMap,
request : request
};
// Render the HTML by adding it to PDICT so that you can get the value later in ISML
if (content.custom.body) {
var html = new StringWriter();
Velocity.render(content.custom.body, global, html)
args.velocitySegment = html.toString();
}
} catch (e) {
Logger.error("[int_aem_core] Error Rendering Content: " + e);
return PIPELET_ERROR;
}
return PIPELET_NEXT;
}
The script renders the Velocity template and writes it to an object available to the ISML template. The following is added to the ISML template to include the segment if it exists. The script assumes you are using a pipeline to render the ISML.
<isif condition="${empty(pdict.velocitySegment)}">
<isinclude template="content/content/htmlcontentasset"/>
<iselse>
<div style="margin-bottom:26px;margin-left:14px;margin-right:14px;">
<isprint value="${pdict.velocitySegment}" encoding="off"></isprint>
</div>
</iselse>
</isif>
Writing out the rendered Velocity Template
If you don't want to write the result directly to the response, include a string writer. This is useful for email templates or including snippets of rendered Velocity in ISML templates.
Example: writing a template to the "contact us" Email
This example can be used with the SiteGenesis contact us email template.
email/contactus.isml
Replace the contactus.isml template in your SiteGenesis instance with an identically named template with the following contents:
<isscript>
importPackage( dw.io );
importPackage( dw.web );
var velocity = require('dw/template/Velocity');
var urlUtil = require('dw/web/URLUtils');
response.setContentType('text/html') // Set the content type to HTML
velocity.renderTemplate('contactus.vs', { // Render the template
res : Resource, // Pass the Resource object to the template (for localized messages)
url: urlUtil, // Pass the URLUtils object to the template (for rendering URLs)
customer: pdict.CurrentCustomer, // Pass the customer to the template (for 'personalizing')
subject: pdict.MailSubject,
form: pdict.CurrentForms.contactus // Pass the form to the template
} );
</isscript>
contactus.vs
This example assumes you have a Velocity template file named
contactus.vs
with the following contents:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<subject>#if($subject)$subject#else $form.myquestion#end</subject>
<head></head>
<body>
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td align="center" style="background:#e0e0e0;padding:50px 0;">
<center>
<table style="background:#ffffff;border:1px solid #999999;width:680px;">
<tr>
<td style="font-size:12px;font-family:arial;padding:20px 10px;vertical-align:top;">
<p style="font-family:georgia;font-size:20px;">Commerce Cloud (Velocity)</p>
<!-- Render the form fields and their localized descriptions -->
<p>$res.msg('contactus.name', 'email', null) $form.firstname $form.lastname</p>
<p>$res.msg('contactus.email', 'email', null) $form.email</p>
<p>$res.msg('contactus.phone', 'email', null) $!form.phone</p>
<p>$res.msg('contactus.ordernumber', 'email', null) $!form.ordernumber</p>
<p>$res.msg('contactus.myquestion', 'email', null) $!form.myquestion</p>
<p>$res.msg('contactus.comment', 'email', null) $!form.comment</p>
<!-- Print a message based on the users authentication state -->
#if(!$customer.isAuthenticated())
<p><b>Become a customer <a href="$url.abs('Account-StartRegister')">here</a>!</b></p>
#else
<p><b>Thank you for being a customer!</b></p>
#end
</td>
</tr>
</table>
</center>
</td>
</tr>
</table>
</body>
</html>
- rendering localized messages (
$res.msg('contactus.name', 'email', null)
) - displaying values (
$form.email
or$!form.phone
) - rendering URLs (
$url.abs('Account-StartRegister')
) - using decisions (
#if(...) ... #else ... #end
) - accessing object data (
!$customer.isAuthenticated()
)
/on/demandware.servlet/webdav/Sites/Dynamic/SiteGenesis
Testing Information
$res.msg('contactus.email',
'email', null)
gets the localized message for contactus.email from the email
resource bundle without using a default message (null).
$!form.phone
shows the phone number only if provided. Without the exclamation mark and no phone number
provided, it shows the text '$form.phone'
.
To test the contactus template:
-
Open the contactus page in SiteGenesis (/
SiteGenesis/contactus
). -
Fill in the form.
-
Click submit.
You receive the updated email at the specified address with the template information.
Using Controllers or Pipelines
A controller can
render a Velocity template directly or call a script to render the
template. A pipeline must call a script to render a pipeline, either
through a script node or in the isscript
tag of an ISML
template.
Creating a controller to render a Velocity template or Snippet
Create a controller with a public function that contains the same code as a script or requires a script module. Salesforce recommends creating a script module as a helper function and calling it from your pipeline. The following is a sample helper function:
Creating a pipeline to render a velocity template or Snippet
Salesforce recommends using controllers rather than pipelines,. However, this section describes what to do if you want to add the ability to publish a layout to your existing storefront, which uses pipelines.
- interaction node - use this to render the ISML template and include the snippets in the ISML template.
- interaction continue node - use this to render the ISML template and include the snippets in the ISML template for forms.
-
one or more script nodes and a stop node - In general, if you don't want to use an existing pipeline or ISML, it's highly recommended that you use a controller in place of this solution. However, if you don't want your Velocity template wrapped in an ISML template and you want to use an existing pipeline, you can use one or more script nodes to render different parts of the page, such as the header, body, and footer.
Rendering different parts of the page in different script nodes effectively replaces the way
isdecorate
is used in ISML. You can use script logic to select different templates for different sections of a page, such as different header templates for an event-driven sale category or an exclusive page for members of a loyalty program.
ISML Equivalents in Velocity
When referencing property values in Velocity, make sure to use the correct case for the actual property name. Using an uppercase letter when the property name starts with a lowercase letter generates an error.
ISML Patterns
The # notation in ISML and ISPRINT replace references that can't be resolved with an empty string. Velocity by default prints the reference itself. To avoid that, use the $!VAR notation.
ISML by default escapes all dynamic content written
to the output according to the content type (HTML by default) to avoid any
XSS problems. Velocity doesn't do that. So all dynamic content needs to
be handled by the EscapeTools provided by Velocity. The syntax then
becomes: $esc.html($!VAR)
.
Comments in ISML are
converted to lines starting with '##
' in
Velocity.
#if($VAR==1)
true
#elseif($FOO && $BAR)
more true
#else
false
#end
An 'isDefined(VAR)
' test is now
'#if($VAR)
', and "hasLoopElements(ITER)
'
gets replaced as '#if($IT.hasNext())
'.
For remote includes, a directive needs to be written manually.
ISML Tags and Their Velocity Equivalents
ISML tag | Velocity tag |
---|---|
isactivedatacontent
|
NONE |
isactivedatahead
|
NONE |
isanalyticsoff
|
NONE |
isbreak
|
#break inside a #foreach
|
iscache
|
use
Response.setExpires() and Response.setVaryBy()
|
iscomment
|
comments in Velocity start with
##
|
iscomponent
|
'<wainclude> ' |
iscontent
|
use
Response.setHeader('Content-Disposition') to
set the content type, use $esc.html($VAR) to
handle HTML escaping
|
iscontinue
|
NONE |
iscookie
|
Response.addHttpCookie()
|
isdecorate
|
NONE |
iselse
|
#if(CONDITION)true#else
false#end
|
iselseif
|
#if(COND1)true #elseif(COND2)more
true#end
|
isif
|
#if(CONDITION)true#else
false#end
|
isinclude
|
#parse for template
includes<wainclude> for remote
includes |
isloop
|
#foreach(ITERATOR)
|
ismodule
|
NONE, use Velocity Macros. Calling
ISMODULES from Velocity templates is not
supported.
|
isnext
|
NONE |
isobject
|
NONE |
isprint
|
$VAR syntax. For formatting, use the Velocity Tools
|
isredirect
|
Response.redirect()
|
isremove
|
NONE, removing variables isn't supported in Velocity |
isreplace
|
NONE |
isscript
|
NONE, scripting isn't supported in Velocity |
isselect
|
NONE, can be done with Velocity |
isset
|
#set
|
isslot
|
NONE |
isstatus
|
Response.setStatus()
|