Common Java Cookbook

Edition: 0.19

Download PDF or Read on Scribd

Download Examples (ZIP)

9.8. Using Macros in a Templating Engine

9.8.1. Problem

You need to reuse portions of a template to standardize the display of common elements such as an address or a name.

9.8.2. Solution

Use Velocity Macro definitions to reuse logic to print out both names and addresses. Velocity macros are like subroutines that take a set of parameters and perform common tasks. In the following Velocity template, two macros, #name and #address, handle the printing of names and addresses:

#set( $volunteer = $appointment.volunteer )
#set( $location = $appointment.location )
#set( $org = $appointment.organization )
## Define the "name" macro
#macro( name $object )$!object.firstName $!object.lastName#end
## Define the "address" macro
#macro( address $object )
$!object.address.street1
$!object.address.street2
$!object.address.city, $!object.address.state $!object.address.zipcode
#end
#name( $volunteer ),
Thank you for volunteering to help serve food at the $location.name next 
week.  This email is a reminder that you are scheduled to help out from 
$appointment.startTime to $appointment.endTime on $appointment.date.  
The address of the shelter is:
#address( $location )    
If you need directions to the shelter click the following URL:
    ${org.baseUrl}directions?location=org.codehaus.cjcook:cjcook-content:jar:0.19
Also, if you are unable to help out on $appointment.date, please let us know by 
sending an email to ${org.email} or by filling out the form at this URL:
    ${org.baseUrl}planschange?appointment=org.codehaus.cjcook:cjcook-content:jar:0.19
Thanks again,
#name( $org.president )
#address( $org )

In the following code, the template shown previously is loaded from a classpath resource organize.vm, and an Appointment object is placed in a VelocityContext:

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
// Create and initialize a VelocityEngine setting a configuration property
VelocityEngine vEngine = new VelocityEngine( );
vEnging.setProperty( RuntimeConstants.VM_CONTEXT_LOCALSCOPE, Boolean.TRUE );
vEngine.init( );
// Create a test Appointment
Appointment appointment = testAppointment( );
// Create a Velocity Context and give it the appointment 
VelocityContext context = new VelocityContext( );
context.put("appointment", appointment);
// Prepare a StringWriter that will hold the contents of
// our template merge
StringWriter writer = new StringWriter( );
// Get a stream to read in our velocity template.  The
// organize.vm file is loaded from the classpath and is stored
// in the same package as the current class.
InputStream templateStream = getClass( ).getResourceAsStream("organize.vm");
Reader reader = new InputStreamReader( templateStream );
// Evaluate the template
vEngine.evaluate(context, writer, "test", reader);
        
// Print out the results of the template evaluation
System.out.println( "organize: " + writer.toString( ) );

The template is merged with a VelocityContext, and the following output is produced:

               John S.,
Thank you for volunteering to help serve food at the Boston Homeless 
Veterans Shelter next week.  This email is a reminder that you are 
scheduled to help out from 9:00 AM to 2:00 PM on Monday, September 
12, 2003.  The address of the shelter is:
    17 Court Street
    Boston, MA 01260
If you need directions to the shelter click the following URL:
    http://www.organize.com/directions?location=2342
Also, if you are unable to help out on September 12th, please let us 
know by sending an email to organize@helpout.com 
or by filling out the form at this URL:
    http://www.organize.com/planschange?appointment=29932422
Thanks again,
Brishen R.
201 N. 2nd Street
Jersey City, NJ 20213

9.8.3. Discussion

A macro definition is started with the #macro directive and ended with #end; the same macro is invoked by calling #<macro_name>( <parameters> ). Velocity macros must be defined before they are referenced, using the following syntax:

#macro(<name> <arguments>)
    <Macro Body>
#end

Macro parameters are not typed as are method parameters in Java; there is no mechanism to check that an Address object is passed to the #address macro, throwing an exception if an inappropriate object is encountered. To successfully render this Velocity template, verify that an Address is sent to the #address macro and a Person is sent to the #name macro.

In the previous example, an instance of VelocityEngine is created and the RuntimeConstants.VM_CONTEXT_LOCALSCOPE property is set to true. This property corresponds to the velocimacro.context.localscope, which controls the scope of references created by #set directives within macros. When this configuration property is set to true, references created in the body of a macro are local to that macro.

The Velocity template in the Solution expects a single reference $appointment to an Appointment bean. Each Appointment has a volunteer property of type Person, and every Organization has a president property of type Person. These Person objects, ${appointment.volunteer} and ${appointment.organization.president}, are passed to the #name macro that prints out the first and last name. Two Address objects, ${appointment.location.address} and ${appointment.organization.address}, are passed to the #address macro that prints a standard U.S. mailing address.

A macro can contain any directive used in Velocity; the following macro uses nested directives to print out a list of numbers in HTML. #numberList allows you to specify a range with $low and $high; values in $numbers within this range will be printed bold:

#macro( numberList $numbers $low $high )
    <ul>
     #foreach( $number in $numbers )
       #if( ($number > $low) && ($number < $high) ) 
           <li><b>$number</b> - In Range!</li>
       #else
         <li>$number</li> - Out of Range!</li>
        #end
     #end
   </ul>
#end

The macro defined above would be called by the following Velocity template. Note the presence of comments, which are preceded by two hashes (##):

#set( $squares = [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] )
## Print out a list of numbers highlighting numbers
## between 25 and 75
#numberList( $squares, 25, 75 )
            

9.8.4. See Also

If your system has a large number of Velocity templates, you can create a set of files to hold common macros, which will be made available to every Velocity template using the velocimacro.library property. For more information, see the Velocity User Guide (http://velocity.apache.org/engine/releases/velocity-1.6.1/user-guide.html#Velocimacros).


Creative Commons License
Common Java Cookbook by Tim O'Brien is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License.
Permissions beyond the scope of this license may be available at http://www.discursive.com/books/cjcook/reference/jakartackbk-PREFACE-1.html. Copyright 2009. Common Java Cookbook Chunked HTML Output. Some Rights Reserved.