In RHUser
, which are classes to simplify the interaction and traversal of Content Server nodes, users, and groups. In 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
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
, andWebNodeAction
); - 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 theadmin.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 theRHObject
subclasses introduced inPart I orPart 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 theDTree
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) )
2
3
4
5
6
7
8
9
10
11
12
13
14
This would output:
>> My name is Chris.
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>)
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)
This will add the following variables to the context:
cgi
- the path to the Content Server CGI (e.g.,/cs/cs.exe
);user
- anRHUser
of the current user;node
- anRHNode of the current node, if applicable;img
- the relative path of the support directory (e.g.,/img/
);support
- the same asimg
;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>
# 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 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')
2
3
Within a template we can access the user
variable with the double-brace notation:
Welcome {{ user }}!
>> Welcome << RHUser: Admin (1000) >>!
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
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.
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
{{ node.parent.categoryvalues.CategoryName.AttributeName }}
>> {'Attribute Value1','Attribute Value2', ... ,}
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 ...
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).
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).
2
Other filters exist for fetching related information. For example, the node
filter fetches the
{{ "2000"|node }}
>> << RHNode: Enterprise (2000) >>
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>
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>
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>
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>
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>
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:
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 importingrhcore/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 themacro
tag:
{% macro helloWorld parm1 %}
Hello World! You passed in {{ parm1|quote }}.
{% endmacro %}
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".
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 theload_base
macro and passes in the parameter value oftrue
(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 withRHTemplate
. 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 value2000
, converts it to anRHNode (using thenode
filter), and sets the value to the context with a variable name ofews
.<table class="rhtable rhstriped">
- Therhtable
andrhstriped
classes are styled by a cascading style sheet that was imported with theload_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 tosort
), 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 ofews
and sort by thesort
parameter passed in by the request. Within the loop thechild
variable is in context.{% usemacro gif child %}
- Thegif
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.