Part III - Template Rendering with OpenText Content Server

In Part I of this blog series I discussed how an object-oriented programming approach can simplify development in Content Server. I introduced RHNode and RHUser, which are classes to simplify the interaction and traversal of Content Server nodes, users, and groups. In Part II I introduced RHModel, which is a base class for defining, persisting, and querying custom objects without having to write any SQL.

In this blog post I introduce RHTemplate, which is a new template language for OpenText Content Server. The language is inspired by the power and simplicity of the Django Web framework, and will look familiar to anyone who knows it.

RHTemplate has many uses. At its root it's a development tool, and can be used anytime data needs to be rendered into text. Consider a few examples:

  • rendering a web page;
  • rendering a personalized e-mail;
  • augmenting the Content Server user interface;
  • building a mobile solution;
  • rendering a RecArray as HTML, CSV, XML, JSON, WordML, or ExcelML;
  • pretty formatting the results of a LiveReport or search; and
  • much more.

The use of RHTemplate isn't tied to any of these uses and can be applied by a developer anytime it's needed. However, I have already successfully integrated it with Content Server in a few different ways:

  • as a Content Server node that can be added, edited, and executed from a web browser;
  • as a CustomView enhancement to insert dynamic content into the CustomView;
  • as a WebLingo replacement (e.g., with RequestHandler, HomeRequestHandler, AdminLLRequestHandler, and WebNodeAction);
  • as an extension to WebReports;
  • as an independent template on the filesystem; and
  • as a template for the rendering of HTML e-mail.

Let me review a few features before getting into the details of how it works.

  • Widgets - RHTemplate is packed with ready-to-use widgets and templates that can be referenced from your template whenever you need them. These include a date picker, user picker, pagination, tabs, and much more. All the JavaScript required to render these widgets are abstracted away, which means you can have a fully functional JavaScript date picker (in the format configured in the admin.index page) without writing any JavaScript.
  • Compatibility - RHTemplate is compatible with all data types commonly found in Content Server (e.g., Assoc, RecArray, etc.). However, the template language shines when used with any of the RHObject subclasses introduced in Part I or Part II. Templates are compatible with WebLingo and WebReports, and can be referenced from either.
  • CSS - The module includes a cascading style sheet that renders tables and other elements in the style of Content Server. For example, the stylesheet handles the two common table patterns in Content Server without having to add "separator" or "spacer" images between cells or rows.
  • Data Access - RHTemplate can execute a LiveReport, fetch a user's assignments, traverse the DTree hierarchy, perform a simple search, and much more. These results can be fetched and rendered directly from within a template.
  • Readability - The template language is highly readable, which makes it easy to debug and see what's going on.
  • Secure - Templates have access to the data and variables that are provided to them, but also run in the context of the user executing the template.
  • Caching - RHTemplate does a lot of caching behind the scenes for optimal performance.
  • Reusability - RHTemplate embraces the don't repeat yourself (DRY) principle, which means common design patterns are abstracted away and made reusable such that you don't need to write the same code over and over again. Tools are available to write your own reusable components whenever patterns are found in your own implementations.
  • Patterns - RHTemplate makes it extremely simple to apply the three most common operations in a table view: sorting, filtering, & pagination.

# How does it work?

A basic RHTemplate rendering requires two things:

  • a template, which is a string containing plain text and template tags; and
  • a context, which holds the variables with which the template will be rendered.

The source of a template can come from anywhere, such as a file on the filesystem, a text document in Content Server, or even a string hardcoded directly in OScript. The context is constructed with the data you wish to render.

Consider a simple example implemented in OScript:

// A simple template string to render the variable "name"
String templateText = "My name is {{ name }}."

// Compile the string into a template object
Frame template = $RHTemplate.RHTemplate.New(templateText)

// Create a context object
Frame context = $RHTemplate.RHContext.New()

// Assign a variable to the context
context.setValueForKey('Chris', 'name')

// Render the template with the context
echo( template.render(context) )
1
2
3
4
5
6
7
8
9
10
11
12
13
14

This would output:

>> My name is Chris.
1

This example demonstrates a variable evaluation (which I will detail in the next section), but also the fundamental process behind every template rendering.

Templates can also reside on the filesystem (in predefined locations) or as a Content Server node (any text-based node). These are constructed by passing two parameters to the constructor:

Frame template = $RHTemplate.RHTemplate.New(prgCtx, <DataID or file>)
1

A context can also be constructed from a request, and will pre-populate the context with relevant variables. For example:

Frame context = $RHTemplate.RHContext.New(request)
1

This will add the following variables to the context:

  • cgi - the path to the Content Server CGI (e.g., /cs/cs.exe);
  • user - an RHUser of the current user;
  • node - an RHNode of the current node, if applicable;
  • img - the relative path of the support directory (e.g., /img/);
  • support - the same as img;
  • request - the current request record;
  • now - the current date and time.

With this we could create a link to the Enterprise Workspace:

<a href="{{ cgi }}?func=llworkspace">Enterprise Workspace</a>
1

# Variables

The {{ name }} and {{ cgi }} expressions in the previous section are examples of a variable evaluation, which is performed be surrounding a variable name with double braces (i.e., "{{" and "}}"). Variables can be of any data type in Content Server, including an Integer, Date, Assoc, List, Record, RecArray, Object, or Frame. It also supports all RHObject subclass such as RHNode, RHUser, Iterator, and a few others I have yet to introduce. Let's look at a few more examples:

// Create an RHUser instance and add it to the context
Frame user = $RHCore.RHUser.New(prgCtx, 'Admin')
context.setValueForKey(user, 'user')
1
2
3

Within a template we can access the user variable with the double-brace notation:

Welcome {{ user }}!
>> Welcome << RHUser: Admin (1000) >>!
1
2

The << RHUser: Admin (1000) >> text is the string representation of an RHUser object, and is output in place of an unhelpful memory address (behind the scenes it calls the string() method on the object, which is analogous to calling the toString() method on a Java object). Templates support key paths (which were introduced in Part I), which allows quick access and traversal of data structures. For example:

Welcome
{{user.name}}! >> Welcome Admin!

{{user.userid}}
>> 1000

{{user.displayName}}
>> Christopher Meyer (Admin)

{{user.firstname}}
has a default group of
{{user.group.displayName}}. >> Chris has a default group of DefaultGroup.
1
2
3
4
5
6
7
8
9
10
11
12

The last example demonstrates how compact the template syntax can be. Think of how many lines of OScript it would otherwise require to fetch the display name of a user's default group. Here we are doing it in a single line.

Consider another example with a Content Server node (represented by an RHNode), and we wish to access a category value on the parent:

{{ node.parent.categoryvalues.CategoryName.AttributeName }}
>> {'Attribute Value1','Attribute Value2', ... ,}
1
2

Key paths also work with the Assoc, Record, RecArray, and List datatypes. For example, the properties of a request can be accessed with:

{{ request }}
>> R<'cookieData'=A<1,?,'BrowseSettings'='Rs+F2c ... ' >

{{ request.func }}
>> ll

{{ request.SCRIPT_NAME }}
>> /Livelink/livelink.exe

{{ request.cookieData.LLCookie }}
>> w/usvsJZKQMifAeI7FxtHUfH ...
1
2
3
4
5
6
7
8
9
10
11

# Filters

Variables can be post processed with a filter. A filter is a function that operates on its input, may accept parameters, and returns a value. It is applied using the vertical bar character ("|") after the variable. For example, the upper filter can be used to convert a string to uppercase:

My name is {{ user.displayName|upper }}.
>> My name is CHRISTOPHER MEYER (ADMIN).
1
2

Filters can also be chained and accept parameters. Parameters are added after the filter name and are separated by colons:

My name is {{ user.displayName|upper|replace:"HRI":"hri" }}.
>> My name is ChriSTOPHER MEYER (ADMIN).
1
2

Other filters exist for fetching related information. For example, the node filter fetches the RHNode representation of its input:

{{ "2000"|node }}
>> << RHNode: Enterprise (2000) >>
1
2

RHTemplate includes many other useful filters for common operations.

# Tags

Tags have a variety of purposes in RHTemplate. These include:

  • iterating collections of data (for loops);
  • conditional statements (if statements);
  • setting values to the context;
  • fragment caching;
  • defining and calling macros;
  • referencing other templates; and
  • more.

Template tags are surrounded by "{%" and "%}" and have a variety of syntaxes. Depending on the syntax, an argument can be a variable (which will be resolved automatically), or a literal (wrapped in single or double quotes).

Let's dive in with an example. Say we have a folder in our context with the variable name node. We can loop the contents of the folder with the for tag and the children property of the node:

<p>The contents of {{ node.name }} are:</p>

<ul>
  {% for child in node.children %}
  <li>{{ child.name }}</li>
  {% endfor %}
</ul>
1
2
3
4
5
6
7

We can sort the children using the sort filter:

<ul>
  {% for child in node.children|sort:"name" %}
  <li>{{ child.name }}</li>
  {% endfor %}
</ul>
1
2
3
4
5

Or, sort the results based on a URL parameter:

<ul>
  {% for child in node.children|sort:request.sort %}
  <li>{{ child.name }}</li>
  {% endfor %}
</ul>
1
2
3
4
5

Or, only show items where the name begins with "Folder" using the filter filter:

<ul>
  {% for child in node.children|filter:"name":"startsWith":"Folder" %}
  <li>{{ child.name }}</li>
  {% endfor %}
</ul>
1
2
3
4
5

# Example

Let's bring it all together with an example that everyone can relate to. Consider a standard browse page in Content Server, and how something similar might look rendered with RHTemplate:

{% include "rhcore/macros.html" %} {% usemacro load_base true %} {% set
"2000"|node as ews %}

<table class="rhtable rhstriped">
  <thead>
    <tr>
      <td class="minWidth">{% usemacro sortheader "subtype" "Type" %}</td>
      <td class="width70">{% usemacro sortheader "name" "Name" %}</td>
      <td class="alignRight">{% usemacro sortheader "size" "Size" %}</td>
      <td class="alignCenter">
        {% usemacro sortheader "modifydate" "Modified" %}
      </td>
    </tr>
  </thead>

  <tbody>
    {% for child in ews.children|sort:request.sort %}
    <tr>
      <td>{% usemacro gif child %}</td>
      <td>{% usemacro browselink child %}</td>
      <td class="alignRight nowrap">{{ child.size }}</td>
      <td class="alignCenter nowrap">
        {{ child.modifydate|date }} ({{ child.modifydate|timesince }})
      </td>
    </tr>
    {% endfor %}
  </tbody>
</table>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Here is a screenshot:

Simple Template Rendering

Let's look at this line-by-line:

  • {% include "rhcore/macros.html" %} - The {% include %} tag is used to import an external template. In this example we are importing rhcore/macros.html, which hosts a collections of reusable macros. Macros are like functions that be repeatedly called to render HTML fragments with different parameters. For example, to define a macro use the macro tag:
{% macro helloWorld parm1 %}
    Hello World! You passed in {{ parm1|quote }}.
{% endmacro %}
1
2
3

This can be rendered with the usemacro tag:

{% usemacro helloWorld "car" %}
>> Hello World! You passed in "car".

{% usemacro helloWorld "boat" %}
>> Hello World! You passed in "boat".
1
2
3
4
5

The rhcore/macros.html template has a number of predefined macros for function menu rendering, library importing, sort header rendering, and more. It is an extremely powerful feature of RHTemplate and should not be overlooked.

  • {% usemacro load_base true %} - This renders the load_base macro and passes in the parameter value of true (the meaning of this parameter is not important now). This macro loads a number of CSS and JavaScript libraries into the page that are included with RHTemplate. These libraries are useful for rendering pages, forms, and widgets in the style of Content Server.
  • {% set "2000"|node as ews %} - The {% set %} tag is used to assign a variable to the context. This line takes the value 2000, converts it to an RHNode (using the node filter), and sets the value to the context with a variable name of ews.
  • <table class="rhtable rhstriped"> - The rhtable and rhstriped classes are styled by a cascading style sheet that was imported with the load_base macro call. These classes render the table in a similar style to that of Content Server (e.g., collapsed borders, grey title bar, striped rows, etc), and provides a much simpler styling than the standard approach used by Content Server.
  • {% usemacro sortheader %} - This macro renders a sort header and handles all the nuances of creating the sort link with the appropriate up or down arrow. The first parameter is used in the URL generation (e.g., &sort=-name) and the second is for the label displayed in the page. An optional third parameter controls the URL parameter name (defaults to sort), but the actual sorting is done in the next description.
  • {% for child in ews.children|sort:request.sort %} - Here we loop through the child elements of ews and sort by the sort parameter passed in by the request. Within the loop the child variable is in context.
  • {% usemacro gif child %} - The gif macro is used to render a subtype icon (with the appropriate mouseovers).
  • {% usemacro browselink child %} - This macro renders a browse link for a node, but also includes the modified image callbacks (the little icons after the node) and the function menu. Of course, macros also exist to render the link, modified image callbacks, and function menus separately if required.

This is just a simple example of what's possible with RHTemplate. As I have shown in the example above, RHTemplate comes with a number of predefined macros for rendering common patterns in Content Server. Best of all, you can always create your own macro wherever and whenever you need them.

I'm using RHTemplate in a few projects with great success. I'm creating richer user interfaces with less code that would otherwise have been tedious, repetitive, and difficult with other tools.

In my next blog post I will expand on some of the features of RHTemplate, including LiveReport execution, pagination, tabs, widgets, and Content Server integrations. Stay tuned!

Questions or comments? Send me an e-mail (chris@rhouse.ch) or leave a comment below.