Velocity Templates Developer Guide

This Velocity Templates Developer Guide is intended as a guide for those wishing to write templates for rendering screens and controls into HTML. Ideally, no more knowledge besides HTML skills (along with the documentation provided here) will be needed to develop and customize templates.

Velocity Templating Language (VTL)

The markup language used to author the templates to be rendered using the Velocity and NVelocity templating engines is called the Velocity Templating Language (VTL). A complete specification of this language can be found in the Velocity User Guide; an essential reference for any velocity template developer.

Template Context

The set of variables and objects provided to, and referenced by templates is referred to as the Template Context. By default, this includes the following:

Conventions and best practices

The following is a list of identified conventions and the best practices to employ when authoring or maintaining templates.

Separation of Model from View

Velocity was designed with the separation of Model from View in mind - templates should not build, manipulate or in any way interfere with the model to be rendered (in this case, the conceptual idea of a 'model' is represented by the Template Context).

While it may be possible to call methods that change the state of the model from the template code, do not do it. The results are likely to negatively impact other templates aggregated by the offending template, as well as lead to a possibly inconsistent model. It is likely that if you find yourself needing to change the objects in the template context provided to you from the templates themselves, then the template context is wrong and/or incomplete.

Match template hierarchy with screen model hierarchy

Velocity provides the ability to import a template into another template, basically in-lining it and providing it with the same template context as the parent template. This is very useful, as it maps nicely to the idea of Screen objects encapsulating ScreenControl objects.

This approach has a lot of benefits, with one of the most obvious being maintainability. If a problem is discovered with a template that renders a particular control, or the control object has been updated and the template needs to reflect this, the change may be made in one template and will carry on to all templates that import the control's template.

Extract common logic into a specific template

Following on from the previous point, the template import functionality can be leveraged to extract commonly used functionality into a separate template, which is then imported by templates as required.

This simplifies the job of less experienced or less technically-inclined template developers, since commonly used functionality which might otherwise be complex to implement, may be provided as a set of library functions (called 'macros' in VTL) in a specific template. By doing this, entire APIs and abstraction layers to the template context may be provided to template developers, in order to deliver an interface to the data model at the right level of complexity.

Do not use the '.' character in property names

It is common practice, especially in build scripts, to use the '.' character as a word-separator in property names. This becomes a problem, however, when the properties in various property files (screens.<locale>.properties, styling.<locale>.properties, and so on) are to be accessible from the VTL code implemented in templates. This is due to the way in which Velocity and NVelocity resolve references to variables that exist in the template context. For example, for the VTL fragment:

${screen.title}

the first thing that the parser will try to do is look for the property title inside the object referenced by screen. Failing this, it will then look for the method screen.getTitle().

Velocity does not allow the '.' character as a part of variable names, which is why it reads the above fragment as 'the property 'title' or the method 'getTitle()' on the object 'screen'', not as 'the variable 'screen.title''.

According to the Velocity User Guide, a variable placed into the template context, or one declared in the VTL, must conform to the following rules to be valid:

Examples

The following is a set of examples for performing commonly required operations and implementing commonly required logic in the templates:

Referencing the Screen model's methods in templates

This is accomplished using the screen's reference name as a VTL variable. Typically, this is screen, although custom logic may override this.

So, for example, the screen object is accessed using:

${screen}

 

A public method exposed on the screen model (such as getControls()) may be accessed as:

${screen.getControls()}

 

Notice that the method call looks exactly the same as the implementation language (in this case Java). This is because Velocity and NVelocity parse the code inside the ${} tags as plain Java/.NET code. Note that in NVelocity, the method calls, object references and variable names are NOT case-sensitive. So,

${screen.GetControls()}

and

${screen.getControls()}

 

are parsed as exactly the same method calls on the same object, allowing templates written for the Java implementation of Oracle Web Determinations to be re-used for the .NET implementation. Note however that templates written for the .NET app will not work for the Java implementation if the capitalization on the method names is different. This is due to NVelocity being case-insensitive while Velocity expects the method names to be in the correct case.

Note also that the code inside the ${} tags is resolved in its entirety, including object references and so on, just like in normal Java/.NET code. For example, if I wanted the ID of the first control in the screen's control list, I would do the following:

${screen.getControls().get(0).getControlID()}

Loops, Conditional, Assignments and Delegation

There are many things going on in this example:

## VTL code to loop through the controls on the Screen object. We do this by rendering
  ## each control in turn, depending on its type.

  ## Grab the list of controls from the screen
  #set( $controlList = ${screen.getControls()} )
  #foreach( $control in $controlList )
      #if( ${control.getControlType().equals("LabelControl")} )
 	#set( $labelControl = $control )
 	#parse( "LabelControl.vm" )
      #end
      #if( ${control.getControlType().equals("QuestionControl")} )
 	#set( $questionControl = $control )
 	#parse( "QuestionControl.vm" )
      #end
  #end
                    
  1. You can see that ## is the single-line comment directive. The multi-line comment directive to start is #** and to end is *#.
  2. We have an example of an assignment operator that is quite self-explanatory. The only thing to watch out for with #set  (as with all VTL directives) is to ensure that there is no white space between the # and the name of the directive. That is, # set(  will fail whereas #set(  will work.
    You will also notice that there is no need to allocate the new variable anywhere; simply having it set to something will allocate it and place it in scope. Obviously, there is no typing either.
  3. The #foreach loop is actually the only loop provided by VTL. Its working should be obvious: $controlList  is set to the list of controls that belong to the screen, and for each iteration of the loop, the $control variable provides a reference to the current element in this list.
  4. The #if statement is a little more interesting in that the use of the ${ ... } notation allows us to test a boolean statement generated by a call on the actual object represented by the $control variable. In this case, we check if the $control variable references a LabelControl object. If so, the first thing we do is set a new variable called $labelControl to reference the $control loop variable, since this object is expected by the LabelControl.vm template we are about to delegate to. Any variables that are in scope in the parent template are automatically added to the delegation template's template context. This is because the #parse directive just copies the contents of the referenced template, and parses the content in-line as VTL.
  5. Do the same for the QuestionControl, and the loop is complete. Notice that the block directives (#if and #foreach), each have a corresponding #end directive.
Object typing

The Velocity Templating Language does not have typecast or type checking operators. Hence, the current way to get around this, at least for the screen controls, is to provide a method getControlType() on the ScreenControl objects, which returns a String. This method returns the (non-absolute) name of the screen control Class. This can be considered the replacement of the instanceof (in terms of Java) operator in VTL.

So, for example, if I wanted to check whether a control object (which I have set to the variable $control) is a TextInputControl object:

#if( ${control.getControlType().equals("TextInputControl")} )
     <!-- Do something here... -->
#end
                    

Caveats and Gotchas

No null reference assignment

Once a variable has been assigned to some value using #set, it cannot then be removed from the context (set to null). If the RHS of a #set directive evaluates to a null reference, that #set directive will be skipped. For example, if the screen object's title field is set to null before it is added to the context, evaluation of the following snippet:

#set( $title = "dummy title" )
The screen's title is : $title 

#set( $title = ${screen.getTitle()} ) 
The screen's title is : $title
                    

 

will result in the following output:

The screen's title is : dummy title 
The screen's title is : dummy title
Macro support in NVelocity

Macro support in NVelocity is currently quite limited; for example, a simple macro to set a text variable based on a single if condition worked under Velocity but failed under NVelocity. At present, it is probably wise not to rely on macros in NVelocity.