Rendering Velocity Templates

You can render Velocity templates using controllers or pipelines. If you want to share functionality between new and legacy cartridges, Salesforce B2C Commerce recommends adding your rendering functionality in a utility script that can be used from either controllers or pipeline script nodes. Commerce Cloud encourages you to use a controller, even if you intend to overwrite existing functionality that is currently in a pipeline. See also Comparing Pipelines and Controllers and Migrating Your Storefront to Controllers for information about creating controllers.

Script Rendering Examples

Regardless of whether you are using controllers or pipelines, you 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.

For information on how to create script modules that can be used by both pipelines and controllers, see Converting Scripts.

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 lookup up the file manually, which you allows 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'});  
Note: There is no caching involved in this example.

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. This includes, but isn'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.

This example shows how to invoke the template and render the generated content into a string for further processing. For example, if you want to use it in an email.
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 provides support for 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 above is the EscapeTool.

For a list of all characters to escape, see http://velocity.apache.org/tools/devel/generic/EscapeTool.html .

Supported Velocity Tools

The following Velocity tools are supported by B2C Commerce:
  • AlternatorTool
  • ComparisonDateTool
  • ConversionTool
  • DisplayTool
  • EscapeTool
  • MathTool
  • NumberTool
  • ResourceTool
  • SortTool
  • XmlTool
  • LinkTool
  • LoopTool
For more information about Velocity Http://Velocity.Apache.Org/Tools/Devel/Generic.Html

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 above 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 above 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>
This template illustrates:
Note: This template must be uploaded via WebDAV to /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:

  1. Open the contactus page in SiteGenesis (/SiteGenesis/contactus).

  2. Fill in the form.

  3. Click submit.

    You receive the updated email at the specified address with the template information above.

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.

If you want to enhance an ISML template with sections of the page rendered using Velocity snippets, create a pipeline with an:–•
  • 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.
If you want to render a Velocity directly, create a pipeline with:
  • 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.

The notation for conditions is:
    #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 of a #foreach
iscache use Response.setExpires()('varyby' isn't supported yet)
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()