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 (opens new window) on classes that are not instantiated as objects, or are just instantiated once for the life of the thread (i.e., a singleton (opens new window)). 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
(opens new window) 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.