Christopher Meyer

Oct 212014
 

Introduction

In my previous blog post I introduced a cascading stylesheet that covers two common page layouts in OpenText Content Server. In this next post I continue the discussion and introduce some of the widgets included with RHCore for adding interactive page components (or “widgets”) to a page.

Let’s consider a few examples of widgets in Content Server:

  • a function menu on a node;
  • a breadcrumb trail to display the location of a node;
  • the tabs in tabbed content;
  • a date picker to select a date;
  • a user picker to find and select a user; and
  • a pagination widget to navigate through pages of content.

Some of these components are available in Content Server, but are often difficult to apply outside the context of where they’re normally used. Sometimes it’s not possible at all.

RHCore provides some common interface widgets, which can be added to a page with just a few lines of code. Let’s jump in.

RHCore Widgets

RHCore provides a collection of widgets that can be added to a page whenever you need them. Widgets are designed to work with few preconditions, which makes them easy to use without having to write code in various locations. In fact, the only precondition to a widget is the initial data and two lines in the header of the template (have a look at Part III if you’re not familiar with RHTemplate; these widgets also work with WebLingo and WebReports):

{% include "rhcore/macros.html" %}
{% usemacro load_base true %}

The first line loads the macro library included with RHCore. Macros are a powerful feature of RHTemplate, which are like functions that can be defined and called (with parameters) to insert snippets of HTML. The second line calls the load_base macro, which adds the JavaScript libraries and stylesheets into the page that are required for the widgets to function. Once these are loaded you can call and render a widget without having to write any extra JavaScript, CSS, or OScript.

Widgets come in different flavours. The most common are form field widgets, which are rendered in RHTemplate using the formfield filter with the following syntax:

{{ initialValue|formfield:"<fieldType>":"<fieldName>" }}

With WebReports this can be done with the RHFILTER sub-tag (which is a small enhancement to WebReports to enable RHTemplate filters and other features in WebReports):

[LL_REPTAG_"initialValue" RHFILTER:"formfield":"<fieldType>":"<fieldName>" /]

Let’s look at a few examples.

Date Picker Widget

A date picker widget makes it easy to select a date with a popup calendar. This can be generated with RHTemplate as follows:

{{ "2015-01-01"|formfield:"date":"startDate" }}

Or, with WebReports:

[LL_REPTAG_"2015-01-01" RHFILTER:"formfield":"date":"startDate" /]

This creates a date picker form field that looks like this:

Again, this works without having to write any extra JavaScript or CSS. Behind the scenes the widget:

  • parses of the initial value (e.g., 2015-01-01) into a date (other formats are supported);
  • adds a “Clear” button to clear out the value;
  • displays the chosen date in the format defined in the system configuration; and
  • synchronizes the selected date to a hidden form field named startDate, which can be consumed by Content Server in a POST request (e.g., D/2015/1/1:0:0:0).

Not bad for three lines of code.

Say you later realize you need a date and time picker instead. This can be accommodated by simply changing the field type to datetime:

{{ "2015-01-01"|formfield:"datetime":"startDate" }}

The result:

The simplicity and reusability of this widget has saved me countless hours of time. I find it much more forward thinking than copying and pasting the date picker code each time I need it. And yes, it works with IE.

User Picker Widget

The standard Content Server user picker is a form field with a user icon. Clicking the icon presents a search page where search criteria can be input to find a user. Once the user is found a select link can be clicked to update the field with the selected user:

This style of “classic” user picker can be rendered with RHCore (say, with “Admin” being the initial value):

{{ "Admin"|formfield:"userclassic":"selectedUser" }}

An alternative widget, which requires fewer mouse clicks, is also available:

{{ "Admin"|formfield:"user":"selectedUser" }}

This renders an “autocomplete” field, where typing part of the user’s log-in name, first name, last name, or e-mail address displays a list of matched suggestions from which the user can be selected:

The widget automatically:

  • manages the AJAX request that applies the criteria;
  • synchronizes the ID of the chosen user to a hidden field (named selectedUser); and
  • formats the display names according to the format configured in the admin pages.

A similar widget also exists for groups. Again, not bad for three lines of code.

Multi Ordered User Picker

I’ve had requirements where multiple users needed to be selected with a user-defined order. A standard development pattern in Content Server is to have plus and minus buttons that submits the existing values to a request handler, adds or removes the row, stores the state in a cache, and reloads the page. I find this overly complex, and so I created a widget that does it all on the client.

The widget is no more difficult to render than any other form widget (using a JSON array for the initial values; an OScript list is also valid):

{{ ["Admin","jdole"]|formfield:"multiuser":"selectedUsers" }}

This renders as follows, which shows the selected users in blue bubbles (which can be removed by clicking the “x”):

Additional users can be found and selected by typing a criteria in the “Search for user…” input field:

Users can also be ordered with drag and drop:

As before, the widget automatically:

  • manages the AJAX request that applies the criteria; and
  • synchronizes the selected users to a hidden field as a sorted JSON array (e.g., [3433,1000]), which can be submitted and decoded on the server (using Web.FromJSON() or something similar).

Again, not bad for three lines of code.

Tabs

Tabs are commonly used in applications to group related views. For example, a Content Server document has tabs named General, Audit, Categories, Versions, etc. that all relate to the document. Tabs allow a user to visualize what’s available and navigate among the different views.

Tabbed views are simple to create with RHCore. This is easiest to show with an example:

{% tabs %}
    {% tab "Tab 1" %}
        This is the content of tab 1.
    {% endtab %}

    {% tab "Tab 2" %}
        This is the content of tab 2.
    {% endtab %}

    {% tab "Tab 3" %}
        This is the content of tab 3.
    {% endtab %}                
{% endtabs %}

This renders as follows:

Only the contents of the active tab is rendered and displayed. The framework manages the URL for each tab, which is just the current URL with a different &tab parameter. For example, clicking on “Tab 2” reloads the page but with &tab=2 in the URL.

A parameter can be passed into the {% tabs %} tag to change the URL parameter name. The individual {% tab %} tags also accept a parameter to control whether a tab should be displayed. For example, say you have a tab that should only display if the current user has system administrator rights:

{% tab "Admin Tab" user.isAdmin %}
    Content that only administrators should see.
{% endtab %}

I’m using this widget in numerous projects with much success, and it has greatly simplified what would otherwise be complicated to implement.

Wrapping Up

The widgets in this post are only a few examples of what is possible. Other widgets include:

  • standard form fields (input, textarea, checkbox, password, select, etc.);
  • node picker;
  • category/attribute picker;
  • generic multi ordered select;
  • sort headers (see Part III for a discussion on sort headers);
  • function menus;
  • breadcrumb trails;
  • user info link (that pops up the user information); and
  • pagination (see Part V for a discussion on pagination).

I’m always adding new widgets whenever a general use-case presents itself.

The widgets have saved me countless hours of development time and have allowed me to focus on the overall solution without getting bogged down in the intricate details of a small component. I couldn’t imagine working without them.

Questions or comments about this blog post or anything else? Might RHCore be something you’d want to use in your projects? Send me an e-mail or leave a comment below.
))

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

Sep 262014
 

Introduction

Say you’re building a page in Weblingo or WebReports. Like most pages in Content Server, it probably contains a table to layout the data. Now you’re staring at the screen and asking yourself: How do I make this look like a Content Server page?

Almost every page in Content Server is rendered with a table in one of two styles. Variations exists, of course, but they’re all more or less:

  • a list view, which summarizes a grouping of related items (e.g., browse view, audit data, etc.); or
  • a detail (or “property”) view, which provides details on a specific item (e.g., General tab, Records Detail tab, etc.); forms are usually rendered in this style as well.

Let’s look at a screenshot of each.

List View

An audit tab lists events in a table:

Detail View

A general tab displays information about a node, but also contains a few form elements:

These types of pages in Content Server are often a mashup of nested tables, inline styles, external CSS, and superfluous table and image elements to add separator lines or padding. It’s often a variation of something like this:

<TABLE WIDTH="100%" BORDER="0" CELLSPACING="0" CELLPADDING="0">
    <TR VALIGN="top">
        <TD CLASS="tblBackground" WIDTH="100%">
            <TABLE WIDTH="100%" BORDER="0" CELLSPACING="1" CELLPADDING="0">
                <TR>
                    <TD class="tblForeground">
                        <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0" WIDTH="100%">
                            <TR>
                                <TD> ... </TD>
                                <TD CLASS="labelVerticalDividerRight" BACKGROUND="`ing`tbl-divider-ver.gif" STYLE="background-repeat: repeat-y;">
                                    <IMG HEIGHT="1" ALT="" SRC="`img`spacer.gif" WIDTH="2" BORDER="0">
                                </TD>
                                <TD> ... </TD>
                            </TR>
                        </TABLE>
                    </TD>
                </TR>
            </TABLE>
        </TD>
    </TR>
</TABLE>

I don’t understand why it’s done this way, but I believe one reason is to add a shadow effect behind the table. Whatever the reason, it’s complex and difficult to work with. Much of it can be simplified.

This blog post introduces a cascading style sheet (CSS), which can be used to easily render a table in a style consistent with Content Server. The stylesheet is compatible with RHTemplate (introduced in Part III), but also with WebReports and WebLingo.

The Stylesheet

RHCore includes a CSS stylesheet named rhcore.css. The stylesheet simplifies the styling of many standard Content Server components, but also contains a number of presentational style classes (e.g., alignRight, floatLeft, width100, etc.) for quick and easy formatting. There is some debate whether presentational style classes are good practice or not, but I won’t get into that here.

Let’s look at how the stylesheet can be used to render a list and detail view:

List Views

A list view rendered with rhcore.css looks something like the following. In the example we are using RHTemplate, but something similar could be done with WebReports or WebLingo:

<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 node.children|sort:request.sort %}
            <tr>
                <td>{{ child.img }}</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>

This renders a table like this:

The main style is applied by the rhtable class, which cascades various styling rules to the table and its elements. Namely, it:

  • adds a border around the table;
  • sets a grey background on the header row;
  • adds a line below the header to separate it from the rows;
  • removes the underline on the header sort links; and
  • sets padding within the cells.

Additional presentational styles are used to do specific formatting:

  • rhstriped – alternates the row colours;
  • minWidth – sets the width to 1%;
  • width70 – sets the width to 70%;
  • alignRight – aligns the cell contents to the right;
  • alignCenter – aligns the cell contents in the centre; and
  • nowrap – prevents wrapping of the cell contents.

Now let’s look at detail views.

Detail Views

Detail views (which I also refer to as property views) are also easy to render with rhcore.css:

<table class="rhtable rhproperties">
    <tbody>
        <tr>
            <td>Key A:</td>
            <td>Value A</td>
        </tr>
        <tr>
            <td>Key B:</td>
            <td>Value B</td>
        </tr>
    </tbody>
</table>

This renders something as follows:

The rhtable class applies all the rules as described before, but we’ve now added the rhproperties class. This class adds the following:

  • a grey background on the odd numbered columns; and
  • a border around the cells.

The rhproperties class also supports multiple columns, which is easy to apply by just adding more cells to the row:

<table class="rhtable rhproperties">
    <tbody>
        <tr>
            <td>Key A:</td>
            <td>Value A</td>
            <td>Key B:</td>
            <td>Value B</td>
        </tr>
        <tr>
            <td>Key C:</td>
            <td>Value C</td>
            <td>Key D:</td>
            <td>Value D</td>
        </tr>
    </tbody>
</table>

This renders as follows:

You can also add a button bar with a <tfoot> section:

<tfoot>
    <tr class="buttonBar">
        <td colspan="4">
            <input type="submit" value="Save" />
            <input type="reset" />
        </td>
    </tr>
</tfoot>

Wrapping Up

I find the stylesheet extremely useful since it allows me to quickly create tables without the fuss of hardcoded inline styles or deeply nested tables. The source is also highly readable, which makes it easy to debug and maintain. How easy is that?

Questions or comments about what you read here? Please leave a comment below.

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

Sep 082014
 

Introduction

OScript scripts behave like other data types. Like an integer, string, or list, a script can be assigned to a variable and passed as a parameter to other functions. For example:

// Assign a script to a variable
Script s = $MyModule.SomeGlobal.SomeScript

// Pass the script to another function
$MyModule.Utils.SomeFunction(s)

A Script variable even has a type:

echo( Type(s) == ScriptType )
> true

Programming languages that behave this way are known as having first-class functions (or “scripts” in our case), which means the language treats functions just like any other data type.

Scripts can also be executed:

// Execute the script with some parameters
Dynamic rtnVal = s(parm1, ..., parmN)

This is a powerful feature of OScript, which permits some novel solutions to certain problems. It surprises me that it’s almost never used.

Passing around scripts this way comes with a few caveats. In this blog post I’ll discuss what needs to be kept in mind, provide some examples, and introduce a way to write simple anonymous functions in OScript.

Just a word about terminology. I interchange the words “function” and “script”, which for the purpose of this post are the same thing.

The problem with “this”

Almost all scripts in Content Server are defined on an object. The object can be thought of as the script “owner”, which is the value of this when the script is normally executed. This setup allows you to access other features and functions on the object with the dot-notation (e.g., .fFeatureName or .someFunction(), which is the same as this.fFeatureName or this.someFunction()).

Things get tricky when you start assigning scripts to variables, passing them into functions, and executing them in a context different to where it was defined. When you do this the value of this becomes the object from where the script was called, and not where the script was defined. This causes problems if the script tries to access any feature or script on this (using the dot-notation) that doesn’t exist in the new context. It causes a runtime error and stack trace.

Let’s look at an example. Say we have the following two functions defined in $MyModule.MathUtils:

// Square an integer
function square(Integer myInteger)
    return myInteger * myInteger
end

// Square an integer (by referencing square) and add one
function Integer squarePlusOne(Integer myInteger)
    return .square(myInteger) + 1
end

From anywhere in the system we could do the following:

Script myScript = $MyModule.MathUtils.square

echo( myScript(5) )
> 25

This works because the square() function doesn’t reference a feature or script on this.

However, we run into problems if we try the same with squarePlusOne. The function references the square function, which is not in context once the script is assigned to a variable and executed outside of $MyModule.MathUtils:

Script myScript = $MyModule.MathUtils.squarePlusOne

// stack trace on the call to square()
echo( myScript(5) ) 

Is there a way to handle this?

Controlling the value of “this”

For years I wondered if there was a way to control the value of this when executing a script (e.g., like the call function in JavaScript). I touched on the subject in my last blog post, but didn’t provide a way to explicitly set the value. I dismissed it as not being possible, but recently stumbled across a way to do it. I assume the syntax has long been forgotten since I’ve only ever seen it used once and can’t find it documented anywhere. Here’s how it works:

Say you have a Script variable named myScript, and wish to execute it with this having the value thisArg. The syntax is:

thisArg.(myScript)(arg1, ..., argN)

Yes, it’s that simple.

What’s interesting is that thisArg can be of any data type, even another script! In other words, you could also do this:

Integer i = 5

// Execute myScript with "this" having the value 5
i.(myScript)(arg1, ..., argN)

Strange!

The syntax means we can rewrite our squarePlusOne example as:

Script myScript = $MyModule.MathUtils.squarePlusOne

Integer myValue = $MyModule.MathUtils.(myScript)(5)

The example isn’t practical, but demonstrates how this can be explicitly set when executing a script. Generally speaking, we could replace $MyModule.MathUtils in the second line with any object in the system as long as it contains a square() function that accepts and returns an integer. For example:

Integer myValue = $MyOtherModule.BetterMathUtils.(myScript)(5)

So where is this useful?

Map Function

A map function applies a function to each element of a collection (a List or RecArray in our case), and returns the results as a list. OScript doesn’t provide such a function, so I added one to RHCore ($RHCore.ListUtils.map). For example, we can square a list of integers by using map and the square function we defined earlier:

List squaredValues = $RHCore.ListUtils.map({1,2,5,6}, $MyModule.Math.square)

echo(squaredValues)
> {1,4,25,36}

It’s a powerful way to operate on each element of a list without having to setup a loop each time.

Unfortunately, we run into problems if we try the same with the squarePlusOne function. By passing $MyModule.Math.squarePlusOne into the map function we change the value of this, which causes the square() call to fail.

For this reason the map function accepts a third parameter to control the this value on the executing script. That is:

List newValues = $RHCore.ListUtils.map({1,2,5,6}, $MyModule.Math.squarePlusOne, $MyOtherModule.BetterMathUtils)

echo(newValues)
> {2,5,26,37}

Neat, huh?

Revisiting the making of “super” calls

In Part IX of this blog series I discussed a way to make super() calls in OScript. You may recall the problem of context when calling super.funcName() directly:

function Dynamic funcName(Dynamic arg1, Dynamic arg2, ..., Dynamic argN)

    Dynamic rtnVal = super.funcName(arg1, arg2, ..., argN)

    // do other stuff

    return rtnVal
end

The problem is that super.funcName() runs in the context of the ancestor and not the class that called it. However, this can be fixed with the syntax we just discussed:

function Dynamic funcName(Dynamic arg1, Dynamic arg2, ..., Dynamic argN)

    // the "this" keyword is implied before the period
    Dynamic rtnVal = .(super.funcName)(arg1, arg2, ..., argN)

    // do other stuff

    return rtnVal
end

I wish I had known about this before! But as an aside, this hasn’t deterred me from needing my own super function (since I need to support the Frame data type).

Anonymous Functions

While researching this blog post I started wondering whether anonymous functions could be supported in OScript. Since scripts are first-class functions, you would sort of expect it to be possible to define a script on the fly just as you would with a string or integer literal.

Anonymous functions are not natively supported in OScript, but I realized something simple could be written in OScript. With a few lines of code I put together a lambda function, which is motivated by the lambda function in Python.

Here’s how it works: The lambda function in RHCore accepts a list of parameter names (as a list or comma-separated string) and a single-line expression. For example:

Script square = $RHCore.OScriptUtils.lambda('x', 'x*x')

This returns a compiled script that looks like this:

function Dynamic lambda(Dynamic x)
    return x*x
end

We can use it directly:

Integer myValue = square(5)

echo( myValue )
> 25

We can also use it with the map function to square a list of integers:

List newValues = $RHCore.ListUtils.map({1,2,5,6}, square)

Or, in an even more compact syntax:

List newValues = $RHCore.ListUtils.map({1,2,5,6}, $RHCore.OScriptUtils.lambda('x', 'x*x'))

Neat, huh?

Let’s look at a more relatable example. In RHCore is a filter function that can be used to filter the items in a List or RecArray. The function accepts a collection and script as parameters, applies the script to each element, and keeps any item where the return value of the script is true.

So let’s suppose we had a RecArray that came from querying the WebNodes view. We now want to filter it in memory to keep the folders:

// Get our nodeRecs from somewhere 
RecArray nodeRecs = ...

// Create a lambda function to filter the records
Script filterFolders = $RHCore.OScriptUtils.lambda('rec', 'rec.SUBTYPE == $TypeFolder')

// Apply the filter
RecArray foldersOnly = $RHCore.ListUtils.filter( nodeRecs, filterFolders )

Or, in a single line:

RecArray foldersOnly = $RHCore.ListUtils.filter( nodeRecs, $RHCore.OScriptUtils.lambda('rec', 'rec.SUBTYPE == $TypeFolder') )

Who would have thought this was possible with OScript?

Wrapping Up

OScript is clearly more advanced than most people give it credit. Who would ever have thought this style of programming was possible? I’m using these tools and conventions more and more in my projects, and find it creates much more robustness with less repetition. It’s what I like.

Questions or comments about what you read? Please leave a comment!

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

Aug 062014
 

Introduction

Many programming languages provide a way to override methods, and from the override call the original method that was overridden. This is a fundamental part of object-oriented programming (OOP), and is often made possible with a keyword like super. It may be hard to believe, but this is sort of supported in OScript.

OpenText Content Server OScript supports some OOP concepts (read about it in Part I of this blog series), but is rarely used. Instead, most implementations are written with static methods on classes that are not instantiated as objects, or are just instantiated once for the life of the thread (i.e., a singleton). This works, perhaps is arguably not optimal, but still permits overrides on its methods.

In OScript you do a lot of subclassing (also known as creating a “child” or “orphan”). You inherit all the functions and features from the parent and can selectively override functions and features to suit your requirements. However, sometimes you just need to make a minor modification to a function without wanting to have a full copy (plus the minor change) in your subclass. This is when a “super” call can be handy.

In this blog post I’ll outline a few ways a “super” call can be made in OpenText Content Server. I’ll discuss how this works in OScript, RHCore, and WebLingo files. By no means is this an exhaustive list. Rather, this post will discuss some OScript concepts and introduce workarounds to the limitations.

A note about terminology: When discussing the object hierarchy in OScript I often interchange the terms “parent” with “superclass” and “child” with “subclass”. They’re the same thing. I also prefer to refer to “OScript objects” as “classes” since in this context they behave more like classes than objects.

Calling “super” in OScript

The super keyword can be used in any script override to call the original script on the parent class. The syntax is simple and could look like the following for an override of a function named funcName():

function Dynamic funcName(Dynamic arg1, Dynamic arg2, ..., Dynamic argN)

    Dynamic rtnVal = super.funcName(arg1, arg2, ..., argN)

    // do other stuff

    return rtnVal
end

The super keyword traverses up the inheritance stack and returns the first ancestor where the function on it differs from the function that called it. The function can then be executed on the returned class.

There is one problem. Calling a function in this manner doesn’t run the script in the context of the subclass. That is, the value of this will be that of the ancestor and not of the class that called it (keep in mind this is implied with any dot-reference e.g., .fFeature and this.fFeature are the same thing). This means if funcName() references a feature or script (e.g., .fCache or .someOtherFunction()) then it will access it on the ancestor and not on the class where the override was made. This is a big problem, and is why I wrote in the introduction that OScript only sort of supports this.

Fortunately, there is a workaround. The this context of a function can be changed by assigning the script to a local variable before executing it. OScript doesn’t support closures and by assigning the script to a local variable we change the value of this when it’s executed. That is:

function Dynamic funcName(Dynamic arg1, Dynamic arg2, ..., Dynamic argN)

    // Retrieve the script from our superclass
    Script superClassFunc = super.funcName

    // Execute it here with "this" being our subclass
    Dynamic rtnVal = superClassFunc(arg1)

    // do other stuff ...

    return rtnVal
end

This is somewhat analogous to the call method in JavaScript, which is used to change the value of this on a function call (albeit with a very different syntax).

Let me demonstrate with an example. Say you create a new Content Server subtype based on the Folder subtype. You orphan the Folder WebNode and LLNode into your module, give it a unique subtype and name, and are almost done. You like the default implementation of Action-Audit() (which displays audit information), but want to add a condition that only administrators can run it. This can be done by overriding the Action-Audit() script and wrapping the super call in a conditional block:

function Void Audit( Dynamic parm )
    Object prgCtx = parm.prgCtx     

    if prgCtx.USession().HasByPassPerm()
        // retrieve and execute the original script using super
        Script superClassAudit = super.('Action-Audit')
        superClassAudit(parm)
    else
        // error
        parm.response.error.message = "You must be an administrator to view this page."
    end     
end

This is much cleaner than overriding the entire Action-Audit() script to make the change, which is something that’s done too often. The advantage to the approach should be obvious: If the original code for Action-Audit() changes then nothing needs to be modified in our override to accommodate the change.

It surprises me how seldom the super keyword is used. Instead, the convention is to create an empty “subclass” function (e.g., SubclassExecute(), llnode.NodeAddVersionSubclassPost(), etc.) that subclasses can override and implement. There isn’t really anything wrong with this, but does limit the depth to which a class can be overridden in this manner.

Calling “super” with RHCore

The OScript super keyword does not work with the Frame datatype. There is good reason for this, but it’s unfortunate since the entire RHCore OOP concept is based on the Frame type (read more about this in Part I of this blog series). To get around this I added a super() function to RHCore, which is similar in idea to the super keyword and works with my Frame type. This syntax is:

$RHCore.Utils.super(this, 'funcName')(arg1, arg2, ..., argN)

A key difference between the super keyword and $RHCore.Utils.super is the return value. The super keyword returns the ancestor object, while $RHCore.Utils.super returns the script from the ancestor. This means we don’t have do anything special to execute the script with the correct context. That is, the value of this will be that of the object.

This means our previous audit example could be rewritten as:

function Void Audit( Dynamic parm )
    Object prgCtx = parm.prgCtx

    if prgCtx.USession().HasByPassPerm()
        // call the original script
        $RHCore.Utils.super(this, 'Action-Audit')(parm)
    else
        // error
        parm.response.error.message = "You must be an administrator to view this page."
    end
end

Finally, a shortcut is available on the base RHObject to make super calls. This is simple and compact:

.super('funcName')(arg1, arg2, ..., argN)

Addendum: Changing the scope of this

After writing this blog post I found an alternative way to control the value of this in a super call (or any call, actually). Have a look at Revisiting the making of “super” calls in my subsequent blog post.

Calling “super” with WebLingo files

It’s a common requirement to change a WebLingo page in Content Server. A novice developer might edit the HTML file directly in place, which is bad practice since it introduces support and upgrade issues. A more experienced developer may use a WebLingo override tool (there are a few floating around) to remap the file to a location in a custom module. These work, but require the entire content of the HTML file to copied over and changed. This forms an intrusive duplication of code and will fall out of sync if a subsequent version of a module changes the original file.

RHCore introduces a new WebLingo override approach that’s easy to use (i.e., requires no OScript to create the override), and provides a means to call the original Weblingo file (i.e., the “super” WebLingo) from the override. This isn’t always useful, but works well if you just need to:

  • change the preconditions of a Weblingo file; or
  • prepend or append content to a Weblingo file.

It’s surprising how often this is all that’s needed in an override. To do this I introduced a function called $RHCore.HTMLUtils.CallSuper, which can be executed from a WebLingo file (including a WebLingo override). It does more or less the same as the WebLingo ;;call directive, but allows the original WebLingo (i.e., the “super”) to be called. The syntax is a bit strange, but this is just a quirk of how OScript compiles WebLingo.

Let’s consider an example where this is useful. In RHCore I wanted to add a mechanism to programatically insert HTML into the header of any page. I approached this by creating a subsystem, which any module can use to register and generate content. The content can be HTML, JavaScript, an iFrame, or anything else that’s valid. To do this without changing core code I overrode the webnode/html/llmastheadcomponent.html file (which is the header of most pages), inserted the execution of the callbacks, and then called the original llmastheadcomponent.html. That looks like this:

;;webscript LLMastHeadComponent( Assoc guiComponents )
    <!-- File: rhcore/webnode/llmastheadcomponent.html -->

    ;Object cb
    ;for cb in $RHCore.CommonheaderSubystem.GetItems()
        `%Lcb.Execute(.fPrgCtx, .fArgs)`
    ;end

    ;// Call the original llmastheadcomponent.html
    `$RHCore.HTMLUtils.CallSuper( .ModHTMLPrefix('webnode') + 'llmastheadcomponent.html', _f, this, {guiComponents})`

    <!-- End File: rhcore/webnode/llmastheadcomponent.html -->
;;end

I’m using this setup to generate an “RHCore Developer Toolbar” (with quick links and fast user switching) and a friendly warning message if any database schema is out of sync. Here’s a screenshot:

I can’t think of a less intrusive way of doing this, and it’s not likely to cause problems if the original llmastheadcomponent.html ever gets changed.

Wrapping Up

My goal with any OScript project is to make my overrides as nonintrusive and as forward compatible as possible. While other methods exist to what I outlined here, I find myself going back to these tools more and more often.

Questions or comments about what you read here? Please leave a comment below.

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

Jun 302014
 

Introduction

OpenText Content Server OScript does not provide native support for enumerated types. In this blog post I’ll discuss why enumerated types are useful, and introduce a simple framework for mimicking the data type with OScript.

So what is an enumerated type? The Wikipedia page on Enumerated types sums it up in the introduction:

In computer programming, an enumerated type is a data type consisting of a set of named values called elements, members or enumerators of the type. The enumerator names are usually identifiers that behave as constants in the language. A variable that has been declared as having an enumerated type can be assigned any of the enumerators as a value.

I like to think of an enumerated type as a set of related constants, where the value of each constant is somewhat irrelevant as long as they are unique within the set. A variable that is declared to be of the enumerated type can be assigned any of the values within the set.

Enumerated types reduce bugs and increase code readability. Take the following OScript as an example (and yes, you do see this sometimes):

if eventType == 1
    ...
elseif eventType == 2
    ...
elseif eventType == 3
    ...
end

This works, but is difficult to read since we have no idea what the values 1, 2, and 3 represent. It’s error prone and difficult to debug.

However, if we had the following (more on the construct later):

$MyModule.EventType.kDelete = 1
$MyModule.EventType.kCreate = 2
$MyModule.EventType.kModify = 3

With this we can rewrite the example as:

if eventType == $MyModule.EventType.kDelete
    ...
elseif eventType == $MyModule.EventType.kCreate
    ...
elseif eventType == $MyModule.EventType.kModify
    ...
end

Much better! This is easier to read since there is no guessing as to what the values 1, 2, and 3 represent. It also provides context to our variable since we know it belongs to the $MyModule.EventType enumerated type.

In broader terms, it doesn’t matter what values kDelete, kCreate, and kModify have. They just need to be defined and unique within the enumerated type.

Enumerated Types in OpenText Content Server

RHCore provides a framework for loosely defining an enumerated type. By no means is this strict (e.g., a developer could set eventType = 101, which isn’t valid). It’s a small inconvenience, but doesn’t deflect from the concept. A developer just needs to ensure comparison and assignment operations are done with the enumerated type and not with a hardcoded integer.

The framework doesn’t deviate far from how Content Server manages the states of tasks (e.g., via $Inbox.InboxUtil). However, the framework I introduce is far more generic and reusable in other contexts.

An enumerated type with RHCore is created by:

  • orphaning RHCore :: RHCore Root :: Enum into our OSpace;
  • adding it to our globals;
  • implementing the elements() function; and
  • executing 0Setup() to generate and assign some features based on elements().

For example, say we orphan Enum into our module, name it EventType, and implement the elements() function to return the following list of elements:

function List elements()
    return {'Delete','Create','Modify'}
end

Running 0Setup() generates the following features, while removing any old features that started with the letter “k” (the “k”-prefix is a convention I picked up from iOS development and denotes a constant):

  • kDelete is set to 0;
  • kCreate is set to 1; and
  • kModify is set to 2.

The values $MyModule.EventType.kDelete, $MyModule.EventType.kCreate, and $MyModule.EventType.kModify become globally visible for use in our module.

This may seem overly complicated for defining some constants. Why not just manually add the constants to $MyModule.Utils or some other arbitrary location? It’s the grouping of these elements that provides context and increases code readability. The framework also provides some additional features, which I’ll outline in the next section.

Example and Additional Features

The Enum class has some additional features to assist when using enumerated types. Let’s look at another example to demonstrate.

The OScript Date package has a function called Date.DayofWeek(), which accepts a date and returns the day of the week as an integer. The convention is 1=Sunday, 2=Monday, etc. The documentation provides the following example:

if ( dayOfWeek == 0 || dayOfWeek == 7 )
    Echo( todayString, " is the weekend!" )
else
    Echo( todayString, " is a weekday." )
end

See the bug? The dayOfWeek == 0 comparison is wrong since 0 is not a valid value. This is the exact type of programming error we avoid with enumerated types.

Let’s introduce an enumerated type for the days of the week and fix the example. I wrote earlier that the values of our constants are somewhat irrelevant, but in this case we want to force our constants to match the convention used by the Date.DayofWeek() function (i.e., 1=Sunday, 2=Monday, etc.).

We orphan Enum into our module, name it DayOfWeek, and implement the elements() function as follows:

function List elements()
    return { \
        {'Sunday', [LLIAPI_LABEL.Sunday], 1}, \
        {'Monday', [LLIAPI_LABEL.Monday], 2}, \
        {'Tuesday', [LLIAPI_LABEL.Tuesday], 3}, \
        {'Wednesday', [LLIAPI_LABEL.Wednesday], 4}, \
        {'Thursday', [LLIAPI_LABEL.Thursday], 5}, \
        {'Friday', [LLIAPI_LABEL.Friday], 6}, \
        {'Saturday', [LLIAPI_LABEL.Saturday], 7} \
        }   
end

This looks different than the elements() function I introduced earlier. This alternate syntax for each element is {elementName, verboseName, constantValue}. The elementName is the element name (same as before), the verboseName is the user-friendly display name (which we XLATE for language compatibility), and the constantValue (optional; defaults to the list index) is the constant value for that element.

With this definition we can rewrite the example as:

if ( dayOfWeek == $MyModule.DayOfWeek.kSunday || dayOfWeek == $MyModule.DayOfWeek.kSaturday )
    Echo( todayString, " is the weekend!" )
else
    Echo( todayString, " is a weekday." )
end

This is clearly easier to read and less error prone.

The name() function is available to return the verbose name of a value. For example:

Integer dayOfWeek = Date.DayofWeek( Date.Now() )
echo("Today is ", $MyModule.DayOfWeek.name(dayOfWeek) )

This would return something like:

Today is Wednesday

Or, if we’re using German (due to the use of XLATES in our definition):

Today is Mittwoch

The Enum class also has a choices() function, which can be used with the RHModel and RHForm frameworks. This allows a model field to be restricted to the values of an enumerated type, and the elements to be rendered as a <select> form field.

For example, a form field that uses the $MyModule.DayOfWeek enumerated type would render as follows:

<select name="myField">
    <option value="1">Sunday</option>
    <option value="2">Monday</option>
    :
    etc.
</select>

I haven’t done much with Enum in my projects yet, but have identified multiple places where I wish I had. This will certainly make things easier going forward.

Questions or comments about this blog post or anything else you read? Please leave a comment or send me an e-mail at chris@rhouse.ch.

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

Apr 212014
 

One of the most tedious and repetitive tasks in OpenText Content Server development is the building of a module configuration page. A configuration page is usually linked from the admin.index page, contains a form, and allows an administrator to set the module preferences to their liking. It’s a process that involves:

  • adding a section in the admin.index page by subclassing AdminIndexCallback (from WebAdmin) and implementing the Execute function;
  • creating an AdminLLRequestHandler request handler;
  • implementing the Execute function to read the existing preference values (usually from the opentext.ini file, KINI table, or $LLIAPI.SystemDataUtil), or setting defaults if configuring for the first time;
  • passing the values to the WebLingo file;
  • implementing a form in the corresponding WebLingo file that adheres to the style of Content Server;
  • populating the form with the values passed down from the Execute function;
  • implementing any special form widgets (e.g., date picker, user picker, node picker, attribute picker, etc.);
  • creating a second AdminLLRequestHandler request handler (often with a “2” suffix) to consume the POST request from the first request handler;
  • implementing the SetPrototype function in the second request handler to match the form fields defined in the first request handler;
  • implementing the Execute function on the second request handler to validate and persist the values;
  • showing an unfriendly error message if an error occurs;
  • redirecting back to the first request handler (or admin.index) on completion; and
  • spending hours debugging and maintaining it.

Bleh.

In the past I created configuration pages by copying the implementation of a similar page and modifying it to my needs. This alone was tedious, error prone, and anytime you copy and paste code you’re almost certainly doing something wrong anyway.

What’s worse is that adding, removing, or modifying a preference meant editing code in multiple locations and spending lots of time debugging it.

In RHCore I’ve added an easier way of doing all this, which works without having to write any OScript, HTML, or Javascript (read more about RHCore here). It assumes a collection of form fields to capture preferences such as a boolean, string, integer, long text, user id, group id, node id, category attribute, etc. While this might not be applicable to all implementations, it does cover a common requirement and also serves as a starting point for something more complex.

Here’s an example of how a configuration page built with RHCore might look:

Let’s jump in with an outline of the approach followed by an explanation of the screenshot example.

Step 1 – Define your Preferences

The first step is to define the configuration preferences you’d like to make available. This is done by subclassing one of the following for each preference. These are located in the RHCore OSpace:

  • RHPreferenceOTINI – a preference stored in the opentext.ini file;
  • RHPreferenceKINI – a preference stored in the KINI table; or
  • RHPreferenceSystemData – a preference stored with $LLIAPI.SystemDataUtil.

Each preference class has the same programming interface, and only differ by how the preference is persisted. This is transparent to the developer and can be extended if something else is required.

On each preference we implement the following features:

  • set fEnabled to true;
  • set fNamespace to something unique (e.g., the name of the module, but the same for all preferences that will be placed on the same configuration page);
  • set fDataType to the data type of the preference (e.g., IntegerType, StringType, ListType, etc.);
  • set fFormField to the form field that will render the preference in the configuration page (e.g., DateFormField, DTreeFormField, KUAFFormField, IntegerFormField, etc.);
  • set fFormFieldWidget (optional) to the form field widget if the default doesn’t suffice;
  • set fHelpText (optional) to contain helpful text about the preference;
  • set fName to the name of the preference (preferably using camelCaseSyntax);
  • set fInitialValue (optional) to the default value;
  • set fVerboseName (optional) to the label that will be presented in the form (if something pretty can’t be inferred from the fName feature);
  • set fChoices (optional) to a list of choices (if the preference should be selected from a <select> list); and
  • set fOrder to an integer for the sort order placement in the form.

Step 2 – Create the Request Handler

The second step is to create the admin request handler to present, capture, and persist the configuration preferences. This is done by subclassing RHTemplateAdminConfigurationRequestHandler from RHTemplate (which is part of RHCore), which is a generic request handler for configuration pages. Four features need to be implemented:

  • set fEnabled to true;
  • set fNamespace to match the fNamespace value used in the preferences defined above;
  • set fTitle to a verbose title (e.g., “MyModule Configuration”); and
  • set fFuncPrefix to a unique prefix.

Call Build OSpace, SetRequestHandlers, restart Builder, and done. There’s no weblingo to implement or OScript to write. The request handler is ready and will:

  • set up the configuration form with the existing values (or defaults if running for the first time);
  • present the form to the user in a style consistent with Content Server;
  • capture the POST request when saving the form;
  • validate the form (with user-friendly error messages); and
  • persist the form values.

Step 3 – Create the Section in the admin.index Page

This is done by conventional means by subclassing AdminIndexCallback (from WebAdmin) and implementing it as normal.

Step 4 – Accessing a Preference from OScript

A developer can access a preference value by calling the valueOf() method on the $RHCore.PreferenceSubsystem subsystem:

Dynamic prefValue = $RHCore.PreferenceSubsystem.valueOf(prgCtx, <namespace>, <name>)

The <namespace> and <name> parameters must match the fNamespace and fName values of the preference being accessed. The return value is cast to the configured data type and will default to fInitialValue if the preference hasn’t been explicitly set yet.

Step 5 – Have a Beer

Rejoice in the time saved. What’s great is that adding, removing, or modifying a preference just means creating a new RHPreference* subclass, deleting a preference, or editing a feature on a preference. Nothing else.

Example

Let’s take a closer look at the example in the screenshot above. In the screenshot I showed a configuration page for five preferences:

  • a toggle to enable or disable the module (stored as a Boolean);
  • a group picker to select the “Administrator Group” for this module (stored as an Integer);
  • a category/attribute picker (stored as a List; i.e., {CatID,AttrID});
  • a text area to set the e-mail message (stored as a long String); and
  • a text field to capture the maximum number of e-mails to send per day (stored as a positive Integer).

The page was created using the steps outlined above, and here is how it looks in Builder (using an 0Setup() script to set the features of the group preference):

The fNamespace feature appears to be missing, but this was set on the parent such that it gets inherited by all the subclasses (e.g., set to rhdemo).

With this definition we can access the group id preference by calling:

Integer groupid = $RHCore.PreferenceSubsystem.valueOf(prgCtx, "rhdemo", "administratorGroup")

Going back to the form: This is built behind the scenes using RHForm (read about RHForm here), which means we get form validation for free. For example, attempting to save a negative value to the “Maximum Emails Per Day” field results in this:

Preferences can be rendered using any of the widgets provided with RHCore. These “just work” without having to write any extra HTML or Javascript. Widgets include a date picker, date time picker, user picker (standard or with autocomplete), multiuser picker, group picker (depicted above), category/attribute picker (also depicted above), and a few others. I will write more about these widgets in a future blog post.

That’s the gist of it. While this blog post outlines the building of a simple configuration page, advanced options and overrides exist to create something more complex when required. What used to take me hours to develop and maintain now takes just a few minutes.

Questions or comments? Please add a comment below or “like” on the LinkedIn post if it brought you here.

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

Feb 142014
 

Updated: 12.2017

Categories & Attributes are a powerful feature of OpenText Content Server. Categories allow custom attributes to be defined, which can then be assigned to documents, folders, or any other node type in the system. Other key features include:

  • definitions can be versioned;
  • categories can be applied to each version of a document;
  • attributes can be multi-valued;
  • simple one-to-many relationships are possible using “sets”;
  • supports various data types (boolean, string, text, long text, user, & date);
  • required attributes can be enforced;
  • custom attribute types can be added (e.g., table key lookup);
  • changes are audited; and
  • category data is indexed and searchable.

This is all possible from the Content Server user interface without having to write any custom code. It’s quite amazing when you think about it.

Things get tricky when functionality or logic needs to be bound to a category or attribute. For example, say a client wants the following to happen:

When the Publish boolean attribute on a document is true and the Publish Date attribute has passed then move the document to a publishing folder. Otherwise, if the Publish boolean is false and Publish Date has passed then notify the document owner with an e-mail.

I used to cringe when I received requirements like this. How the heck do you programatically work with categories & attributes? After years of trying and many half-baked attempts, I finally have a solution I’m happy with.

Let’s first discuss what’s available.

The OScript API for Categories & Attributes

I’ve seen a lot of code to manipulate categories & attributes and most do it wrong. Let me explain.

Content Server provides the $LLIAPI.AttrData class for working with categories. It’s an object-like design in a similar manner to what I describe in Part I of this blog series. A Frame instance of AttrData encapsulates the category and attribute information of a node, and provides methods to operate on the data. Saving the category data commits the data stored in .fData to the database.

So what’s the problem? The difficulty is with the programming interface provided by $LLIAPI.AttrData. It seems the interface is incomplete and oriented towards specific types of web requests. It provides no simple way for a developer to set, get, or query values, which is why I assume so many developers access the .fData structure directly:

attrData.fData.{CatID,VersionID}.Values[1].(SetID).Values[SetIndex].(AttrID).Values[AttrIndex] = newValue

Let’s discuss why this is bad. In the example there are no checks that CatID, VersionID, SetID, SetIndex, AttrID, or AttrIndex is valid, in range, or if newValue is of the correct data type. Any wrong assumption about the parameters, structure, or values can corrupt the data and cause all sorts of problems. I’ve seen it done countless times, is highly error-prone, and is why I think most developers are doing it wrong.

To reiterate something I quoted in Part I from Wikipedia on object-oriented programming:

Objects can be thought of as encapsulating their data within a set of functions designed to ensure that the data are used appropriately, and to assist in that use. The object’s methods typically include checks and safeguards specific to the data types the object contains. An object can also offer simple-to-use, standardized methods for performing particular operations on its data, while concealing the specifics of how those tasks are accomplished.

Why should this be any different with AttrData? In my opinion the API should:

  • provide setter and getter methods that are easy to use regardless of the data type, if it’s multi-valued, or within a set;
  • abstract away the need to traverse, manipulate, or even look at .fData;
  • do all the required checks and validation to enforce data integrity;
  • manage auditing (AttrData doesn’t audit automatically); and
  • provide an interface to query on attribute values, independent of IDs, and without having to write any SQL queries.

Some of these features are already available on AttrData, but are incomplete. For example, the ValueSet() method can be used to set a value to an attribute and has the following interface:

function Assoc ValueSet(List spec, Dynamic value)

The spec parameter is a List that defines the attribute to manipulate. That looks something like this: {{29502,95},{1,1,11,2,12,2}}. The numbers refer to the category ID, category version ID, set ID, set index, attribute ID, and the attribute index. The function doesn’t handle auditing and fails if a multi-valued attribute or set hasn’t been extended to accommodate the index. It’s not developer friendly at all.

Introducing RHAttrData

To fix this problem I created a subclass of $LLIAPI.AttrData called $RHCore.RHAttrData and added methods to implement the requirements listed above. None of the extensions modify the original code, which means $RHCore.RHAttrData can be used in place of $LLIAPI.AttrData without side-effects. The advantages come with the added methods. For example, the SetValue() method can be used to set an attribute value and does the following:

  • validates the value is of the correct type (e.g., date, integer, etc.) and attempts to cast it if not;
  • handles auditing;
  • adds workarounds to common misuse of the API that could otherwise corrupt the category (it’s easy to do if you’re not careful);
  • automatically extends the row count of any multi-valued set or attribute (when required); and
  • adds the category to the node if not already applied.

It also has a friendlier programming interface:

function Assoc SetValue(Dynamic CatID, Dynamic AttrID, Dynamic value, Integer AttrIndex=1, Integer SetIndex=1)

With this a developer can set a value to an attribute:

Assoc results = attrdata.SetValue(CatID, AttrID, "New Value")

Or, set the 2nd value of a multi-valued attribute on the 3rd row of a multi-valued set:

Assoc results = attrdata.SetValue(CatID, AttrID, "New Value 2", 2, 3)

You’ll notice the interface doesn’t accept a set ID. The extension determines if an attribute is contained within a set and handles it for you. You just need to provide the attribute ID.

Getting a value is just as easy:

Assoc results = attrdata.GetValue(CatID, AttrID)

if results.ok
    value = results.Value
end

To save the changes:

results = attrdata.commit()

The commit() method makes the appropriate call to llnode.NodeCategoriesUpdate() without having to do any extra DAPINODE or llnode lookups. It also handles auditing without having to manipulate the fAttrChangePrefix or fValueChanges features on attrData (which is otherwise necessary for auditing to work).

Many other methods are available for performing operations such as cloning of categories, setting an attribute to read-only, profiling the properties of the category (e.g., what type of attributes it contains), rendering for a custom form, validating required attributes are fulfilled, etc.

Attribute Identifiers

In the examples I pass CatID and AttrID as parameters to the SetValue() and GetValue() functions. These are just the category DataID and attribute ID.

The difficulty with using IDs is that they are inherently different among Content Server installations. Hardcoding these values will certainly cause problems once the module is installed on a different system.

One workaround is to retrieve the category and attribute IDs from the category and attribute name. However, this can lead to ambiguous cases when the category name is repeated within a system (I’ve seen this happen) or the attribute name is repeated within a category. It’s also unreliable since any user with the appropriate permissions could rename a category or attribute.

To deal with this problem RHCore introduces the concept of identifiers and attribute identifiers. An identifier permits a unique string to be mapped to a DataID. It’s similar to a nickname, but can only be viewed or modified by a user with Administrator rights.

An attribute identifier extends the identifier concept to attribute IDs by allowing each attribute in a category to be mapped to a unique string. With the category identifier it becomes possible to reference a specific category and attribute without having to hardcode any IDs. The only prerequisite is for the Administrator to setup the identifier mappings on each target system (which only needs to be done once).

Querying

Querying on Content Server attributes requires complex and complicated joins with LLAttrData. The query also depends on category and attribute IDs, which means a SQL statement must be refactored if it’s to be used on another system.

RHCore abstracts this away with a simple extension to $RHCore.RHNodeQuery (see Part XVII for a blog post about querying), which permits filtering by categories and attributes without having to write any SQL. For example, to query the system for all documents having a date attribute value in the future:

Frame query = $RHCore.RHNodeQuery.New(prgCtx) \
                .filter('subtype', '==', $TypeDocument) \
                .filterAttribute(CatID, AttrID, '>', Date.Now())

The filterAttribute() method does all the necessary joins to make the query work, and also accepts identifiers as described in the previous section. The convenience, reliability, and amount time saved with this feature cannot be overstated.

Other features and Wrapping up

The extensions have made some interesting solutions possible. One example is the transformation of the category structure into an Assoc and caching the result with Memcached. This permits lightning fast access to attribute values without having to instantiate a Frame or query the database. I’m using this to display attribute values in table views with extremely fast performance.

Many other convenient methods exist. However, most of the time I just use the setters and getters without having to concern myself with the complex details behind them. The extensions have become a cornerstone in my work and makes dealing with categories and attributes almost as easy as any other persisted variable in OScript.

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

Jan 282014
 

List views are an important component of the OpenText Content Server interface. In a nutshell, a list view provides a summary of related information and is often rendered in an HTML table. Consider a few examples of list views in Content Server:

  • folder browsing;
  • audit tab;
  • assignments list;
  • user lists;
  • group membership list;
  • task list;
  • LiveReport;
  • etc.

The majority of list views benefit when operations are available to help find and organize the contents of the view. Common operations are sorting, filtering, and paging. Let’s review:

  • Sorting. Sorting is usually applied by clicking a column header. This causes a page refresh or AJAX request to fetch the sorted information from the server. In some situations the sort can be applied by the client (with JavaScript) without making a new server request.
  • Filtering. With large list views it can get difficult to find what you’re looking for. A filter allows a user to type in some text or select a rule (e.g., a date range) to reduce the result set.
  • Paging. A single page with thousands of items provides an unfriendly browsing experience. A page with millions of items takes long to render and will likely cause the browser or server to crash. Pagination reduces the number of items on the screen, gives visual indication that more information is available, and allows the user to jump to a page by clicking a page number or “Next” button.

Unfortunately, Content Server doesn’t provide a generic approach to apply these operations. Although Content Server has some of these features in their views, the solutions are proprietary to the view and can’t be reused in other contexts. In other words, the basic logic behind sorting, filtering, and paging is rewritten each time it’s required.

Introducing Paginator

To address this problem I created the Paginator class, which is a component of the RHCore framework. The class wraps a RecArray and provides methods to assist with sorting, filtering, and paging. Let’s jump in with an example:

// for brevity we skip error checking
RecArray recs = CAPI.Exec(prgCtx.fDbConnect.fConnection, "select * from DTree")

// construct a paginator instance
Frame paginator = $RHCore.Paginator.New(recs)

A Paginator object has a number of useful methods:

// returns the total number of Records
Integer totalNumberOfItems = paginator.count()

// set the page size to 25 items per page
paginator.setPageSize(25)

// fetch the number of pages, which will depend on the pageSize
Integer pageCount = paginator.pageCount() 

// apply a filter and reduce the set to items where
// the "name" column contains the string "Enterprise"
paginator.filter('Name','contains','Enterprise')

// pageCount will return a different value than before now that a 
// filter has been applied
pageCount = paginator.pageCount()

// set the page number we'd like to view
paginator.setPageNumber(3)

// check if we have a next page (i.e., page 4)
// (this method is useful when rendering Previous/Next buttons)
Boolean nextPageExists = paginator.hasNext()

// sort the contents by name, descending order
paginator.sort('-name')

The sorted and filtered items for the page are retrieved with the items() or iterator() method. The items() method returns the page as a RecArray while the iterator() method returns an Iterator object (see the section on iterators in Part II of this blog series).

RecArray myPagedFilteredSortedItems = paginator.items()

// or

Frame myPagedFilteredSortedIterator = paginator.iterator()

This means we can render the Paginator object in our WebLingo or RHTemplate by iterating the output of one of these methods. The developer still needs to create the user interface components to interact with these features, but this is easy to do with RHTemplate. RHTemplate has a filter, sort, and paginator filter, which can be used to apply filtering, sorting, and paging to a data set directly from within a template.

For rendering the developer can use the provided paginator.html template widget:

{% include "rhcore/paginator.html" with paginator=paginator pageParm="page" %}

This will render a basic pagination component with the page count, item count, and highlighted page in the following style. It also manages the URL generation as defined by the pageParm parameter (e.g., &page=3):

A widget is also available for creating sort headers with the appropriate URL and up/down arrows. More information on the sort header macro can be found in Part III of this blog series.

Extensions

You may have recognized a problem with the previous example: What if the select * from DTree call returns a million rows? This would certainly cause performance problems and likely crash the server. To get around this I created a subclass of Paginator called RHQuery, which behaves like Paginator but manages everything at the database level. To the developer it has the same interface except for the constructor, which looks like this:

Frame paginator = $RHCore.RHQuery.New(prgCtx, "select * from DTree")

Everything else in the example is exactly the same. The difference is that sorting, filtering, and paging are handled by the database and makes using the control possible over a million items. Behind the scenes this is done by extending the SQL query before making the database call. Provisions are in place to prevent SQL injection and to ensure parameters are valid (e.g., the sort parameter is a valid column). This means a developer can set parameters on the Paginator object directly from a request value. For example:

paginator.sort(request.sort)

paginator.setPageSize(request.pageSize)
paginator.setPageNumber(request.pageNumber)

Similarly, a subclass of RHQuery called LiveReportQuery is available and allows a Paginator object to be constructed from a LiveReport. The object ignores the “Record Limit” field and allows the data from a LiveReport to be punched up with filtering, sorting, and paging without having to embed any of that logic in the LiveReport itself.

Wrapping Up

The Paginator class and its subclasses are all key-value compliant and work seamlessly with RHModel and RHTemplate. I’m using it in a number of projects and with just a few lines of a code can add paging to any list view that requires it. It’s efficient, reusable, and convenient. I couldn’t imagine doing it any other way.

Questions or comments? Leave a comment below.

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

Jan 202014
 

HTML forms are a cornerstone of the OpenText Content Server user interface. They are used throughout the system to capture user input and submit the data back to the server.

In part IV of this blog post series I will review how forms are typically managed in Content Server, discuss their limitations, and offer an alternative approach using the RHForm framework. Just to be clear: This post isn’t about any of the Content Server form modules (e.g., eForms, Forms, PDF Forms, or Web Forms); I’m referring to standard HTML forms that are embedded in the WebLingo files. You know, these things:

<form method="POST" action="/livelink/livelink.exe">
    ...
    <input type="submit" />
</form>

Forms are deceivingly complex when you consider their full lifecycle. Consider the pieces involved:

  • setting up the request handlers (usually two);
  • fetching any initial data;
  • passing the initial data down to the WebLingo;
  • rendering the form and the form fields (e.g., input, select, text area, radio buttons, etc.) with its initial data;
  • layout of the form (e.g., table, inline, CSS, etc.);
  • client-side JavaScript (e.g., client-side validation, date pickers, user pickers, node pickers, autocomplete, etc.);
  • server-side validation (e.g., integer fields contain only numbers, required attributes are not blank, custom validation rules, etc.); and
  • casting of values (e.g., “1000” to 1000).

Only once a form has passed validation can the form values be used (e.g., saved to a table, execute a LiveReport, initiate a workflow, write to the opentext.ini, etc.).

Content Server accomplishes most form tasks with a brute force programming approach. That is, each form in the system is manually constructed and code is added in multiple locations for it to fit together. Let’s review how some of this works.

Forms are usually tied to a request handler and hardcoded in the associated WebLingo file. Forms are often embedded in a table and styled with some CSS, but also include formatting elements such as “spacer” images, “separator” cells, “thin line” rows, and tables within tables for a shadow effect. Not only does this approach bloat the page, but it makes it difficult to maintain over the hundreds of WebLingo files that have adopted this approach. It’s an unfortunate case of copy and paste programming.

Forms are closely tied to the data it’s rendering and this is hardcoded right in the WebLingo. For example, consider the generation of a select form element to select a group:

<select name="group">
    ;for group in groups
        <option value="`group.ID`" 
            ;if group.ID == selectedValued
                SELECTED
            ;end
        >`group.name`</option>
    ;end
</select>

This type of coding ties the form element to the groups variable, limits reusability, and is error-prone since the same pattern is repeated each time a similar form element is required.

Content Server provides some reusable form widgets. A node, user, and date picker are available, but they are clumsy to use and require adapting your form to the precise prerequisites of the widget.

Form validation is one of the weakest components of Content Server, which is surprising since forms are so vital to the user interface. Validation is usually performed by a second request handler (which I refer to as the consuming request handler) prototype, which is a list of rules the request must satisfy. It means duplicating the form field names in the prototype list, setting the expected data types, and flagging each field as mandatory or optional. It does not validate the validity of index values (e.g., a user ID field is actually that of a user) nor provide a callback for custom validation rules (e.g., field A and field B cannot both be blank). These types of rules must be validated directly in the request handler. Worst of all, forms that do not validate return a jarring error page and requires the user to click the browser back button to recover. This can be confusing to the user and may cause them to lose their work. How often have you seen this?

In summary, consider the steps to add a new field to an existing Content Server form:

  • fetch the related data in the request handler;
  • pass the value down to the WebLingo;
  • add the form element and pre-populate it;
  • add any related JavaScript (e.g., date picker, autocomplete, etc.);
  • modify the consuming request handler prototype to validate the value; and
  • modify the consuming request handler to do something with the validated value (write to table, save in opentext.ini, etc.).

This scattered implementation over multiple locations makes the form implementation repetitive, difficult to maintain, have limited reuse, error-prone, and a violation of the don’t repeat yourself (DRY) principle. Is there an easier way?

Introducing RHForm

The RHForm library is a framework for simplifying and managing the form lifecycle, and is an important part of the RHCore framework. It is inspired by the Forms API of the Django Web Framework, to which I’m grateful for showing me how to do this. The framework introduces the RHForm object, which encapsulates a number of form-related tasks:

  • field definitions (including the data type, rendering widget, whether it’s required or optional, help text, etc.);
  • rendering of the HTML form;
  • pre-population of the field values;
  • widgets for user, node, and date picking (without additional JavaScript);
  • user-friendly form validation and error handling;
  • easy methods for accessing the validated form data (cast to the correct data type); and
  • tools for managing multi-valued forms.

The framework also changes the form submission pattern typically used in Content Server. Instead of having two request handlers (often named myfunc & myfunc2), everything is encapsulated in a single request handler. This keeps the logic contained and makes user-friendly form validation possible.

Defining a form

Forms can be defined and created in a number of ways, but the simplest is to subclass the RHForm class and implement the FormFieldInstances() method. This is similar the setting up a model (see Part II for information on the RHModel framework) and could look like this for a simple login form:

function List FormFieldInstances()
    return { \
        ._('StringFormField').New(.fPrgCtx, 'username'), \
        ._('PasswordFormField').New(.fPrgCtx, 'password') \
        }
end

Here is a screenshot:

The form class is added to the global scope with an “RH” prefix (this can be configured). Once defined, a form instance can be created in the request handler with the New() constructor:

Frame loginForm = $RHMyLoginForm.New(prgCtx, request)

The loginForm object contains methods for rendering the form, consuming the POST request, validation, and methods for fine tuning the form behaviour. Let’s review and put it together.

Rendering a form

Form instances can be rendered by using one of the available rendering methods. One such method is the as_table() method, which renders the form in a format suitable for placement in a table. For example, calling loginForm.as_table() returns:

<tr><td>Username: <img src="/img/required.gif" /></td><td><input type="text" id="id_username" name="username" value="" placeholder="Required" /></td></tr>
<tr><td>Password: <img src="/img/required.gif" /></td><td><input type="password" id="id_password" name="password" value="" placeholder="Required" /></td></tr>

With this the HTML form can be created by passing the loginForm instance to the WebLingo and implementing it like this:

<form method="POST" action="`request.SCRIPT_NAME`">
    <input type="hidden" name="func" value="`request.func`" />
    <table>
        <tbody>
            `%LloginForm.as_table()`
        </tbody>        
        <tfoot>
            <tr>
                <td colspan="2">
                    <input type="submit" value="Login" />
                    <input type="button" value="Cancel" />
                </td>
            </tr>
        </tfoot>
    </table>
</form>

The developer has the freedom to style the form and table however they want, or use the stylesheet included with RHCore for a nice looking default.

Validating a form

The same request handler used to generate the form is also used to validate the submitted values in the POST request. This is achieved by placing the validation and consuming code in a conditional block that only executes when the request is a POST.

Form validation is performed by calling the isValid() method. This does three things:

  1. consumes the values of the POST request and saves it to the form;
  2. validates and casts each field to the correct type, but also sets an error state on any field that fails to validate; and
  3. returns true if all fields are valid, false otherwise.

This means when isValid() returns true the form values are valid, are cast to their correct type, and are ready to be used for whatever purpose the form was created. The cleanedData() method can then be used to retrieve the validated values. For example:

Frame loginForm = $RHMyLoginForm.New(prgCtx, request)

if $WebLL.WebUtils.IsPostRequest( request ) // only consume POST requests
    if loginForm.isValid()
        String username = loginForm.cleanedData('username')
        String password = loginForm.cleanedData('password')

        // do something with username & password

        // we're done, redirect the browser elsewhere
        .fLocation = ...
    end
end

Here is the cool part: If the form does not validate then isValid() returns false and the redirect does not occur. The request handler continues and renders the WebLingo just as it did before. However, the as_table() method now renders the form with the user-friendly error messages.

For example, the initial login form looks like this (rendered with the rhcore.css stylesheet included with RHCore):

The user inputs their username and clicks the login button:

This submits the form back to the request handler. The isValid() method returns false since the password field is required but was left blank. The request handler continues to execute and renders the WebLingo as before, but now with as_table() including the user-friendly error messages:

You’ll also notice the username field value wasn’t lost between the requests. The isValid() method saves the POST values to the form such that the subsequent rendering retains the values (including any invalid values).

Changing the form behaviour

Let’s fine tune the form behaviour with some examples.

Suppose it is determined that passwords are optional and can be left blank. The setRequired() method can be used in the field definition to mark a field as optional:

function List FormFieldInstances()
    return { \
        ._('StringFormField').New(.fPrgCtx, 'username'), \
        ._('PasswordFormField').New(.fPrgCtx, 'password').setRequired(false) \
        }
end

With everything else being the same, the form will now render the field without the required icon and a more appropriate placeholder:

Of course, the isValid() method will no longer return false if the password field is left blank.

Another example are initial values. Initial values can be assigned to a field with the setInitialValueForKey() method. In our login form we could make “Admin” the default username by calling:

loginForm.setInitialValueForKey("Admin", "username")

More options exist to fine tune how the form and fields should behave. For example, methods can be overridden on the RHForm subclass to permit custom form validation (e.g., the password must be six characters in length, must contain both numbers and letters, etc.).

Other features

Integration

The framework is a component of the RHCore framework and is fully compatible with RHTemplate and RHModel. Form definitions can be inferred from your model definition and bound to an instance, which makes it easy to create forms to add and edit model data. I’ll detail how this works in a future blog post.

Widgets

The framework includes a number of form widgets that can be used without having to write any extra JavaScript. This includes the following:

  • DTreeFormField – a form field that pops up a “TargetBrowse” window to select a Content Server node;
  • KUAFFormField – a form field that selects a user with autocomplete. For example, replacing our StringFormField in our login form to KUAFFormField gives us:

  • KUAFFormField2 – a traditional Content Server user picker;

  • DateFormField & DateTimeFormField – form fields with a date picker that respects the system date format as configured on the admin.index page;
  • MultiUserFormField – a form field for rapidly finding and selecting multiple users; and
  • more!

These widgets have saved me countless hours of coding and are very easy to use. Widgets can also be rendered in an RHTemplate independent of a form instance. For example, to create a date picker with the user’s birthday as the initial value:

{{ user.birthday|formfield:"date":"birthdayFieldName" }}

This would produce (assuming the user has a birthday on 29 February 1984):

Date Picker

Again, no extra JavaScript coding is required for this to work.

Stylesheet

The rhcore.css stylesheet has a number of stylings to render forms in a Content Server compatible way. Of course, a developer can create their own stylesheet if the rhcore.css defaults don’t suffice.

Complex forms

Forms are often more complex than what has been demonstrated so far. Consider Content Server category & attributes, which contain multi-valued attributes, sets, multi-valued sets, and multi-valued attributes within multi-valued sets. RHForm provides a number of extensions to make complex forms like this possible.

Generic Admin pages

The forms on most admin pages are quite repetitive. For this I created an RHForm extension for rapidly defining and rendering custom admin configuration pages without having to write any WebLingo. It’s reusability at its best.

Wrapping up

This introduction is only the beginning what’s possible with the RHForm framework. I’m using it on a few projects to create complex forms that are easy to generate, validate, and maintain. It has saved me countless weeks of development time to create interfaces that would have been very difficult otherwise.

Want to know more? Add a comment or send me an e-mail at chris@rhouse.ch.

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

Nov 242013
 

Introduction

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.

  • WidgetsRHTemplate 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.
  • CompatibilityRHTemplate 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 AccessRHTemplate 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.
  • CachingRHTemplate does a lot of caching behind the scenes for optimal performance.
  • ReusabilityRHTemplate 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.
  • PatternsRHTemplate 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) )

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 – 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>

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')

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

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

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.

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', ... ,}

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 ...

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).

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).

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

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

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>

We can sort the children using the sort filter:

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

Or, sort the results based on a URL parameter:

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

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>

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>

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 %}
    

    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".
    

    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.

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.