Using JavaScript in Oracle DRM

Avatar

By
February 9th, 2016


Beginning in DRM release 11.1.2.3, JavaScript has been added as a means to process business logic on derived properties and to create validations; though this blog post will focus on the former.

For those of you with experience writing JavaScript in the context of a web browser, keep in mind that the Document Object Model (DOM) of HTML and XML documents is not available, but rather DRM has its own object model used to reference various DRM objects like versions, hierarchies, nodes, and properties.

JavaScript is not only faster than the legacy DRM formulas; it is also easier to read, has more capability, and can store comments in the code for self-documentation.

This blog post was written using an instance of DRM 11.1.2.4.321.

Learn JavaScript

The intent of this blog post is not to teach you JavaScript. There are countless resources out there that can get you up and running quickly; codecademy is a great option. The purpose of this blog post is to help you understand how to access the objects in DRM (versions, hierarchies, nodes, property values, and property objects), and how to leverage some of the methods available.

Data Types

The standard JavaScript data types are available in DRM, and the DRM Admin Guide is very valuable to understand how the various property data types are converted to JavaScript data types. Two of the most common property data types are string and Boolean, which have direct conversions in JavaScript.

Developing & Testing Scripts

Your derived JavaScript properties will be processed when your exports are run. However, re-running an export each time you make a small change to a script is not an efficient way to develop and test. Luckily, there is sandbox functionality directly in the property creation GUI.

Log in to DRM and navigate to the Administer section

New -> Property Definition

I’ve create a Global Node property that is Derived using Script (i.e. JavaScript), and have left the data type as a String.

Click on the Parameters tab to start writing. If you haven’t yet saved the property, do so now or you will see this message.

The return Statement

return [[expression]];

The return statement ends script execution and returns the expression as the resulting property value. Keep in mind you need to return an appropriate data type for the property being processed. For example, if your property is data type string, you should not return an integer or Boolean object. A string value should be enclosed in either single or double quotes.

The famous first program can be used to demonstrate a valid script on a string data type property in DRM…

Oops, we haven’t selected a node with which to evaluate the script. This is an important concept because all of the objects in DRM are accessed through the node currently being evaluated.

Click on the … for Selected Node. Select a Version, then a Hierarchy, and lastly a Node. Click Evaluate again.

The print Function

Print( [[expression]] );

The print function is invaluable for developing and debugging your scripts. In the following example, “Hello, World!” would be displayed in the warnings section of the script editor window in DRM. Without a return statement, the Evaluation Results will always be undefined.

In this example, our “Hello, World!” string is being held in a variable called x and then printed to the Warnings section. We’ve also added a variable y that is holding a famous English pangram and being returned as our Evaluation Results.

For simple scripts, using the print statement probably isn’t necessary. However, when your script is going through more than a couple steps to reach the end result, the print statement allows you to see the value of your variable(s) in the intermediary steps.

Keep in mind that whether you are developing your script in the sandbox here, or running an export in Production, the print statements will be executed in both scenarios. For that reason, be sure to comment these out prior to moving your code into Production.

The node Object

Almost every object in DRM is accessed through the node object; Local and Global node property values, Hierarchy property values, and Version property values. In addition to accessing the values of property definitions, you can also access the property definition objects themselves. This would allow you for example to see if a property is overridden on a particular node, or the data type of that property.

The PropValue Method

return node.PropValue(“ [[Property Definition Name]] “);

To save space, and to enable you to copy/paste these bits of code, I am only going to screenshot the Evaluation Results and Warnings sections going forward.

In this application, I have created a property called Custom.DRM.MemberName. This property is used to strip off the node’s prefix (and suffix if it is shared). If you don’t understand why you would use prefixing in node names, please refer to my series Getting Started with Oracle DRM. I can access that property’s value using the following code; please note that quotes (single or double) are required around the Property Definition name.

return node.PropValue(“Custom.DRM.MemberName”);

In every DRM application, there is a system property called Core.Abbrev that is used to store the node’s name, which is a Global Node property definition. I can access the node name this way:

return node.PropValue(“Core.Abbrev”);

This works fine, but there are actually shorthand ways to reference many of the System properties, for example, this code will produce the same result:

return node.Abbrev;

Global and Local Node Context
Local Node properties can always reference Global Node properties; however, the opposite is not always true. Node Name (Core.Abbrev) is a Global Node property and each name must be unique within a Version. For every node in your application, there exists a single Global Node object. A node can however exist multiple times within a hierarchy through sharing (with a suffix like “:Shared-001”) and can even be inserted into multiple hierarchies (in which case the node name can be the same because it is technically the same node). When you insert an existing node into another hierarchy, it’s descendants will move with it.

If you need to access the name of the hierarchy that a node exists within, this can only be accessed by a local node property. A node’s parent in a hierarchy is another example that can only be accessed by a Local node property. This is a tricky concept to explain, but you need to be aware of it because if you have a Global Node property value and you think your script is correct but it keeps returning undefined, it could be a contextual issue.

When deciding if the property you’re creating should be Global or Local, ask yourself, “is this property specific to a specific node in a hierarchy under a specific parent?”. If so, this should be a Local Node property. If the property should have only a single value for the Primary and all Shared nodes, and for the node inserted into another hierarchy, then it should be a Global Node property.

Short Hand References to Common Global and Local Node System Properties

These System properties are accessible by both Global and Local Node properties. These are the most common properties, for the complete list please refer to the Oracle DRM Admin Guide.

print(node.Abbrev + " = Node Name");
print(node.Descr + " = Node Description");
print(node.Leaf + " = Boolean: Is this node a Leaf?");
print(node.Inactive + " = Boolean: Is the node Inactive?");
print(node.IsPrimary + " = Boolean: Is this the Primary node?");
print(node.IsShared + " = Boolean: Is this a Shared node?");
print(node.Approved + " = Boolean: Has this node been Approved?");

Short Hand References to Local Node System Properties

These system properties are accessible by Local Node properties only. If I run these commands on the Custom.Test.Script.Global property that I have been using, I will get the results below. Each line comes back undefined because all of these properties are specific to a Local Node object.

print(node.Parent + " = LocalNodeObject for the parent node of this node.");
print(node.ParentNodeAbbrev + " = Name of parent node");
print(node.HierAbbrev + " = Core.HierAbbrev (Hierarchy Name)");
print(node.Hier + " = HierarchyObject for the hierarchy the node is in");
print(node.Level + " = Number representing the node’s level in the hierarchy");
print(node.Primary + " = The primary node for this shared node. If the primary is not in this hierarchy, returns the primary in the first hierarchy in which it occurs.");

I went ahead and created another property Custom.Test.Script.Local. Everything is the same except for the Property Level which is Local Node. When I run the same commands, I get this result:

The Hierarchy (Hier) Object

There may be times when you want to access the Hierarchy properties applicable to the node being evaluated. Again, there are short-hand methods to access some of the System properties, and you can also access Custom properties that you create using the following code. In this example, we are accessing a Hierarchy property called Custom.Hier.TestProp using the PropValue method:

return node.Hier.PropValue(“Custom.Hier.TestProp”);

Keep in mind that Hierarchy properties can only be accessed using a Local Node property.

Short Hand References to System Hierarchy Properties

print(node.Hier.Abbrev + " = Hierarchy Name");|
print(node.Hier.Descr + " = Hierarchy Description");
print(node.Hier.NodeCount + " = Hierarchy Node Count");
print(node.Hier.SharedNodesEnabled + " = Boolean: Are shared nodes enabled?");
print(node.Hier.TopNode + " = Hierarchy Top Node");

The Version Object

You can also access properties specific to the version object that a node exists within. In every application I develop, I create a Version-level property called Custom.Ver.Name, and I include that property in all of the system exports. When you open an export table to view data, it is important to understand which version in DRM that information came from, and what time the export was run. The JavaScript for Custom.Ver.Name is as follows:

return node.Version.Abbrev;

The PropValue method is also available to access your custom Version properties. For example:

return node.Version.PropValue(“Custom.Ver.Name”);

You can access Version-level properties from both Global and Local node properties.

Short Hand References to System Version Properties

print(node.Version.Abbrev + " = Version Name");
print(node.Version.Descr + " = Version Description");
print(node.Version.HierCount + " = Version Hierarchy Count");
print(node.Version.NodeCount + " = Version Node Count");

The Sys Object

The last type of object we are going to discuss is the Sys object. Within a DRM application, one Sys object is created that is applicable to all contexts and provides some methods that can be quite useful. I’m going to give examples of the 2 methods that I find most useful.

GetSysPrefValue(abbrev)

This method allows you to get the value of a System Preference. This is being used in the Custom.DRM.MemberName property that is used to strip off prefixes from the node name, and suffixes for shared nodes. Obviously you could hard-code the suffix delimiter into your script but that is not good coding practice because if the System Preference changes at any time in the future, the script will also need to be updated. The better approach is to pull the suffix delimiter directly from the System Preference like this:

return Sys.GetSysPrefValue("SharedNodeDelimiter");

IsNodeBelow(descendant,parent)

I’ve found this method to be useful when I needed to apply different business logic based on which ancestor a node exists under. This method takes 2 parameters and will return a Boolean result; descendant which we want to be the node currently being evaluated, and parent which is the ancestor we want to know whether the node is a descendant of or not. Since this method is evaluating the structure of a hierarchy, it will only work on a Local Node property.

To get the descendant, we are going to use a method called NodeByAbbrev which will access a Local Node Object by it’s name:

node.NodeByAbbrev(node.Abbrev)

To get the parent we’ll also use the NodeByAbbrev method but this time we are going to hard-code the node name because the logic will be specific to a given ancestor:

node.NodeByAbbrev(“regn^West”)

To put the whole thing together:

return Sys.IsNodeBelow(node.NodeByAbbrev(node.Abbrev), node.NodeByAbbrev("regn^West"));

I first ran this on my Custom.Test.Script.Global property and received the following result. Again, this method cannot be used on a local node property.

I ran the same code again on Custom.Test.Script.Local and received this result:

Typically, this method would be used in an IF block, here’s an example of some code that will process different logic on a store (can use the prefix or node type to identify) based on the region it is in:

var curNode = node.NodeByAbbrev(node.Abbrev);
var centerRegn = node.NodeByAbbrev("regn^Center");
var eastRegn = node.NodeByAbbrev("regn^East");
var southRegn = node.NodeByAbbrev("regn^South");
var westRegn = node.NodeByAbbrev("regn^West");

if ( node.PropValue("Custom.DRM.Prefix") == "store" ) {
	if ( Sys.IsNodeBelow(curNode,centerRegn) ) {
		return "This store is in the Central Region"; }
	else if ( Sys.IsNodeBelow(curNode,eastRegn) ) {
		return "This store is in the Eastern Region"; }
	else if ( Sys.IsNodeBelow(curNode,southRegn) ) {
		return "This store is in the Southern Region"; }
	else if ( Sys.IsNodeBelow(curNode,westRegn) ) {
		return "This store is in the Western Region"; }
	else { return "This store doesn't exist in a region"; }
}
else { return ""; }

LocalNodeObject Methods

We’ve already looked at the PropValue method which we’ve seen can be used to access properties values at the Node, Hierarchy, and Version levels. We also used the NodeByAbbrev method as part of our Sys method IsNodeBelow. To wrap up this blog post, I am going what I call the “With” methods of which there are 4: AncestorsWith, ChildrenWith, DescendantsWith, and SiblingsWith.

For each of these methods, you have to define your own function in the script that will evaluate each of the ancestors (or children, or descendants, or siblings) and return either true if the node object should be included in the resulting array, or false if it should not. You could for example write a script that returned a list of a node’s siblings that all have a certain prefix.

As an example, I’m going to write a script using the AncestorsWith function to determine which region a store exists within.

AncestorsWith(function,maxResults,searchFromTop,inclusive)

This method takes 4 parameters and returns an Array of LocalNodeObjects:

  • function = (required) The name of a function that you declare in your script which must have a Boolean result
  • maxResults = (optional) defaults to 1 and indicates the max number of nodes that should be included in the resulting array, use 0 for no limit
  • searchFromTop = (optional) defaults to false, set to true if you want to search from the top of the hierarchy down, but typically this will be false
  • inclusive = (optional) defaults to false and indicates whether the current node will be evaluated as part of the result set, typically this will be false

In the Entity hierarchy I am working in, each of the 4 regions has a prefix of “regn”, and they are the only nodes to have this prefix. The function that we create is going to process each store’s ancestor nodes (starting with its parent) until is finds a node with this prefix. A store can only have 1 region so our resulting Array will contain a single node object. Once we have our result set, we are going to use a JavaScript Array method pop() to get the LocalNodeObject out of the array so we can access its properties. The result of this property will be the region’s member name (name without the prefix) and it just so happens we have a property to do this string manipulation for us (Custom.DRM.MemberName).

// first we declare a functional called checkNode
// each ancestor node object is passed into a parameter called curNode so it can be evaluated
function checkNode(curNode) {
	if ( curNode.PropValue("Custom.DRM.Prefix") == "regn" ) { return true; }
	else { return false; } }

// only run for members in the Entity hierarchy that are stores
if ( node.HierAbbrev == "Entity" && node.PropValue("Custom.DRM.Prefix") == "store" ) {
		
	// variable to store resulting array from AncestorsWith function
	var resArray = node.AncestorsWith(checkNode,1,false,false);
	
	// if the resArray is not null then we know a node was found
	if ( resArray != null ) {
		var resNode = resArray.pop();
		var res = resNode.PropValue("Custom.DRM.MemberName");
		return res; }
	
	// if a node was not found return an empty string so doesn't return undefined
	else { return ""; }
}
else { return ""; }

In this script I am creating a variable called resArray to store the resulting Array. It is important to evaluate that Array before trying to use the pop() method because if nothing was found it will error out. You also want to make sure to limit the script execution to only the applicable nodes because that will make your application run faster. In this case, I’ve checked to ensure the node is in the Entity Hierarchy and has a prefix of “store”. All other nodes will jump straight to the default case and will return an empty string.

That’s it for today, I hope you found this blog post helpful, please leave questions in the comments and I will do my best to answer those in a timely manner. Happy coding!


Avatar

About TopDown Team

The TopDown Team includes members of TopDown Consulting who want to let the community know about webcasts, conferences, and other events. The team also conducts interviews on various EPM industry topics.

Leave a Reply

Your email address will not be published. Required fields are marked *