Velocity Template Help

From DBSight Full-Text Search Engine/Platform Wiki

Table of contents

Velocity Template for DBSight 1.x

Editing Template Files

Velocity

DBSight 1.x template is based on Velocity, which is "a Java-based template engine. It permits anyone to use the simple yet powerful template language to reference objects defined in Java code". Please refer to its User Guide (http://jakarta.apache.org/velocity/user-guide.html) for how to use Velocity.

Macros

#errorMarkup

The #errorMarkup macro displays the server-side error (stored in the $error variable) on the web page.

#escapeHTML

Replaces characters having special meaning inside HTML tags with their escaped equivalents, using character entities such as '&amp;'. The escaped characters are: < > " ' \ &.

Example:

 #escapeHTML($doc.get('content'))

Its Java equivalence is

 $escapeChars.forHTMLTag($doc.get('content'))

In the case of passing escaped characters to another macro, you need to enclose it with a pair of double quotes:

 #highlight("#escapeHTML($doc.get('content'))")

Or you can use the following:

 #highlight($escapeChars.forHTMLTag($doc.get('content')))

#highlight

Highlights query terms in the specified field.

Example:

 #highlight($doc.get('album_title'))

The above example enables highlight on the album_title field.

#summarize

Summarizes and highlights the specified field (similar to the Google snippet).

Example:

 #summarize($doc.get('content'))

The above example enables summarization on the content field.

#templateInclude

The #templateInclude macro parses and renders the specified template file. The path of the included template file should be relative to that of the calling file.

Example:

 #templateInclude("pageHeader.vm")

#useHighlightTag

Specifies the hightlight style.

Example:

 #useHighlightTag('<span style="background-color:yellow">' '</span>')

The above example makes the background of the highlighted terms appear yellow.

Velocity Tools

$date

Formats date or time variables. Please refer to DateTool (http://jakarta.apache.org/velocity/tools/javadoc/org/apache/velocity/tools/generic/DateTool.html) for detailed usage.

Example:

 $date                         -> Oct 19, 2003 9:54:50 PM
 $date.long                    -> October 19, 2003 9:54:50 PM PDT
 $date.medium_time             -> 9:54:50 PM
 $date.full_date               -> Sunday, October 19, 2003
 $date.get('default', 'short') -> Oct 19, 2003 9:54 PM
 $date.get('yyyy-M-d H:m:s')   -> 2003-10-19 21:54:50

$math

Performs math in Velocity. Please refer to MathTool (http://jakarta.apache.org/velocity/tools/javadoc/org/apache/velocity/tools/generic/MathTool.html) for detailed usage.

Example:

 $math.add(1, 2)  -> 3
 $math.sub(10, 3) -> 7

$number

Formats number variables. Please refer to NumberTool (http://jakarta.apache.org/velocity/tools/javadoc/org/apache/velocity/tools/generic/NumberTool.html) for detailed usage.

Example:

 $myNumber                              -> 13.55
 $number.format('integer', $myNumber)   -> 13
 $number.format('$#,##0.##', $myNumber) -> $13.55

DBSight Tools

$httpUtil

String appendQuery(String query, String name, String value)

Modifies a URL (or part of the URL) by appending the new value to the existing value of the specified name. If the name does not exist, a new name/value pair will be appended to the URL.

Parameters
query - the URL to modify
name - parameter name
value - parameter value
Returns
the modified URL

Example:

 appendQuery("a=123&b=345", "a", "789")

returns "a=123+789&b=345".

Variables

Variable Type Description
$docs Array documents returned by the search
$indexName String index name
$q String the user-entered search query
$HTMLEncodedQuery String encoded search query in which all applicable characters are converted to HTML entities
$URLEncodedQuery String equivalent to java.net.URLEncoder.encode(q, "utf-8")), where q is the search query
$searchTime String the search time in seconds
$start Integer the index of the first result on a page
$length Integer the number of results per page
$total Integer the total number of results
$dc.sortableColumns ArrayList columns that are sortable
$sortBy String the current sort by column name
$queryString String the query string that is contained in the request URL after the path, same as javax.servlet.http.HttpServletRequest.getQueryString()
$noStartQueryString String the query string with parameter "start" removed
$isMultiSearch Boolean Test whether in multiSearch mode or not

Loop Variables

In Velocity, the #foreach element can loop over a Vector, a Hashtable or an Array. For example,

<table>
#foreach( $doc in $docs )
 <tr>
  <td>$velocityCount</td>
  <td>$doc.get("album_title")</td>
 </tr>
#end
</table>


displays a list of album titles returned by the search. $velocityCount is the loop counter variable reference, starting at 1 by default.

Loop Variable Description
#foreach( $doc in $docs ) $doc.get(column_name) returns the value of the specified column
#foreach( $c in $dc.sortableColumns ) $c.columnName returns the column name

Extending Templates Utilities

All Velocity template utility classes are loaded via file

/WEB-INF/conf/toolbox.xml

Here is one example to load $date into context

<tool>
 <key>date</key>
 <scope>application</scope>
 <class>
  org.apache.velocity.tools.generic.DateTool
 </class>
</tool>

Sometimes you may need additional functionalities. There are two ways to add new utility classes.

  1. Edit toolbox.xml file to load new utility classe
  2. Using toolLoader to load, like $toolLoader.load("com.abc.Helper")

For more details, you can check http://www.jajakarta.org/velocity/tools/velocity-tools-1.1/docs/generic/ToolLoader.html

Analysis of narrowBy.vm

narrowBy.vm is one the most interesting templates of all. It provide categorized results count of search results. So users can quickly narrow down search results by any category he selected.

The default implementation should be enough for most setups. But for special requirements, customization are needed. But luckily, it isn't too difficult, really. Let us explain the structure of the narrowBy.vm.

First of all, the default narrowBy.vm is the same for all indexes. The code looks like this:

 1: #if($filterIndexName == $indexName )
 2: #foreach( $columnCount in $filterColumnCounts)
 3: #set($columnIndex = $velocityCount - 1 )
 4: #set($c = $dc.filterableColumns.get($columnIndex))
 5: #set($_isSelfFiltered   = $filteredColumns.indexOf($c.columnName.toLowerCase())>=0 )
 6: #set($_isParentFiltered = $c.filterParentColumnName && $filteredColumns.indexOf($c.filterParentColumnName.toLowerCase())>=0  )
 7: #if($columnCount.size()>0 && (!$_isSelfFiltered && (!$c.filterParentColumnName || $_isParentFiltered) ) )
 8: <table class="box">
 9:   <tr><th>
10:     Narrow By#if($c.displayName) ${c.displayName}#else ${c.columnName}#end
11:   </th></tr>
12:     <tr><td><span style="font-size:10pt;">
13:       #foreach( $cKey in $columnCount.keySet())
14:         #set($doubleQuote= '"')
15:         #set($t1=$httpUtil.appendQuery($queryString, 'q', "$c.columnName:${doubleQuote}$!{cKey}${doubleQuote}"))
16:         #set($t2=$httpUtil.addOrSetQuery($t1, 'start', null))
17:         + <a href="?$t2">$cKey(${columnCount.get($cKey).value})</a><br />
18:       #end
19:     </span></td></tr>
20: </table><br />
21: #end
22: #end
23: #end

Line 1, 23: if

$filterIndexName: which index the narrowBy/filtering is applied to. Useful in multi-index cases.
$indexName: current index name

Counting for each category is cached when paging through results. But this may be a problem if the user switched to another index the system has. These two variables are to determine whether the count in the context is good or not.

Line 2, 22: foreach

Loop through all filterable columns. We may mix-use filterable columns/category/narrowBy columns, they are the same thing.

$dc.filterableColumns: array of filterable columns
  $filterColumnCounts: array of counts for each filterable column

The two arraies should be of the same size.

Line 3: set

set $columnIndex so it can start from 0. $velocityCount starts from 1

Line 4: set

get one filterable column as $c

Line 5: set

set $_isSelfFiltered to true if column $c is already filtered.

$filteredColumns : array of columns which is already filtered in the input query.

For example, if the user typed in "abc state:California", category counts for each state don't need to be displayed.

Line 6: set

set $_isParentFiltered to true if the parent column of column $c is already filtered.

$filteredColumns : array of columns which is already filtered in the input query.

For example, category counts for each city don't need to be displayed, unless parent column "state" is already filtered, like "abc state:California".

Line 7,21: if

check if we should display results counts. The conditions are

  1. $columnCount is not empty, see line 2
  2. not already filtered in the query, see line 5
  3. no parent column, or parent column is already filtered, see line 6

Line 10: if

use $c.displayName, which is set in the configuration page by UI, or $c.columnName, which is the column's original name.

Line 13, 18: foreach

go through the $columnCount(A java.util.Map) by get its ketSet().

Line 14: set

A small trick to escape double quote that need to be displayed.

Line 15: set

Using a home made httpUtil to append the additional query to the original $queryString, formatted as

column:"value"

Line 16: set

Using a home made httpUtil to reset start point for pagination to empty.

Line 17: display it!

Whew!

FAQ

How to get a list of values from $doc object?

The question:

I don't know how to include a loop in our .vm template to send back all these values :

This is what I currently have :

 #foreach( $doc in $docs )
 <result>
   <artist_id>$!{doc.get('ARTIST_ID')}</artist_id>
   <artist_name>$escapeChars.forHTMLTag($!{doc.get('ARTIST_NAME')})</artist_name>
 ...

With this syntax, I can only get one ARTIST_NAME for each column.

Ideally, I would like to generate the following XML :

 <artists>
   <artist>
     <artist_id>1</artist_id><artist_name>George Clooney</artist_name>
   </artist>
   <artist>
     <artist_id>45</artist_id><artist_name>Madonna</artist_name>
   </artist>
 </artists>

The answer:

Inside the $docs loop, you can use $doc.getValuesList('ARTIST_ID'), $doc.getValuesList('ARTIST_NAME') to get two ArrayList objects.

To loop through the two ArrayList objects (an similar example can be found in narrowBy.vm), you can use

 #set($ids = $doc.getValuesList('ARTIST_ID') )
 #set($names = $doc.getValuesList('ARTIST_NAME') )
 #foreach($id in $ids)
   #set($columnIndex = $velocityCount - 1 )
   <artist>
     <artist_id>$id</artist_id><artist_name>$names.get($columnIndex)</artist_name>
   </artist>
 #end

How to Debug Template

It's sometimes useful when you can see all the available variables passed into Velocity context. Here is a simple script you can use if you want to.

#foreach( $Attribute in $request.AttributeNames )
 $Attribute : $request.getAttribute($Attribute) <br/>
#end

Is there a way to reuse the same template for multiple indexes?

Question: I am aware that we can copy a template but what we are looking for is not to maintain multiple copies of the same. Answer: You can create a template with main.vm, and use

#templateInclude("some_template.vm")

to include a partial template. This partial template can be reused by other main.vm by

#templateInclude("../../indexName/templateName/some_template.vm")

You need to make sure all the indexes uses matching field names.

What are "reserved template files?" and what variables are available within each file?

Actually only main.vm and cache.vm are reserved.

documents.vm is a recommended file name.

documents.vm is to render each searched document within main.vm. Usually it only needs $doc variable, which is set in the loop

#foreach($doc in $docs)

There is a document specific variable helper in documents.vm page.

cache.vm is to directly render the document into a whole page. It's only used when you have a link to display the cached item. Only $doc is available.

main.vm, documents.vm, and other pages usually have all variables available.

You can find variables and their usage in the generated example templates.

How to add this/that functions in Velocity?

If you a little java, jou can use java libraries to do this. To load create java objects, DBSight uses toolloader

http://tomcat.jajakarta.org/velocity/tools/velocity-tools-1.1/docs/generic/ToolLoader.html

Find some example, like the first line in "JSON" templates,

#set($result = $toolLoader.load("org.json.simple.JSONObject"))

So you can create a small java object, load it by the toolloader(it'll call the object's constructor, you need to create the constructor.). And then you can do all sorts of things, including the regular expression, http requests, etc.

How to use regular expression to trim strings in Velocity?

Take URL as an example:

In java, String object has a replaceFirst function:

String replaceFirst(String regex, String replacement)

You can use this

 #set($url = $doc.get('location') )
 #set($url = $url.replaceFirst('http://',) )

to remove "http://'. Or

 #set($url = $doc.get('location') )
 #set($url = $url.replaceAll('http://',) )

A more obvious way to get the last part of URL is:

 #set($url = $doc.get('location') )
 #set($lastIndex = $url.lastIndexOf('/') + 1 )
 #if($lastIndex>=0)
   #set($url = $url.substring($lastIndex) )
 #end

How to display null values?

Presently if a database column contains a null, my template displays ${doc.get('mycolumnname')} on the page.

you can define one in velocity, like

#macro( show $anyvalue)
  #if $anyvalue
    $anyvalue
  #else
  #end
#end

And then you can use it like

#show($doc.get('mycolumnname'))

How to include contents from other url?

This is a good way to include contents from other urls, which could be produced via PHP, Perl, etc.

#set($httpUtil = $toolLoader.load("net.javacoding.xsearch.utility.HttpUtil"))
#set($external_url = "http://www.google.com")
$httpUtil.open($external_url)