Javascript Libraries

November 29, 2021

Introduction

The more Automation Scripts you write, the more you will find code repeating across those scripts. This can cause bits of similar code copied across many, many scripts. It also means that if a problem is found in this code, you must hunt through the system and find all the occurrences, which is an activity no one enjoys.

In this post we will see how functions from one Automation Script can be imported and used in another and how this allows for creating libraries for common functions. If you are unfamiliar with functions in Automation Scripts, check out our previous post here.

Invoking a Script

The basic pattern for invoking another script is quite simple: Using the implicit service variable, call the invokeScript function, passing in the target script name. In the example below, we are invoking an Automation Script named SHARPTREE.LIB.

service.invokeScript("SHARPTREE.LIB");

Calling Functions on the Result

When you invoke the script as we did above, the invokeScript function creates a new context, puts the current mbo implicit variable in that context, and then invokes the script. After invoking the script, the context is returned from the function and now contains the called script's functions and defined variables. The implicit variables however, are only available to the script as it is invoked and are not available to functions that are returned on the context. To access to the implicit variables within a function you must either assign the implicit variable to a local variable as part of the non-function execution or add them as arguments to the function.

Note that all code not within a function will be executed when invokeScript is called. For library scripts I recommend that you ensure all your code is encapsulated in functions.

We will start with a simple example with a basic function call and then move on to more complex scenarios with a custom context and variable handling.

For the first example we are going to assume we have an Automation Script called SHARPTREE.LIB that has a function called add that takes two values and returns their sum as shown below.

function add(value1, value2) {
return value1 + value2;
}

Knowing that invokeScript returns the context with the script's functions, we can now call the add function as shown below.

var result = service.invokeScript("SHARPTREE.LIB").add(1, 2);

After the SHARPTREE.LIB add function is called, our variable result will have a value of 3.

Using a Custom Context

In the previous example, the script will be invoked with only the mbo implicit variable passed in, creating and returning a new context for the script execution. If you want to pass in other variables to the context, such as including the service implicit variable you need to first create an execution context, then put the service variable into context and finally invoke the script with the context. The context is a Java Map type, so an import is necessary. Details on importing is covered in our previous blog here.

// import the Java HashMap
HashMap = Java.type('java.util.HashMap');
// create a new HashMap to use as the execution context
var library = new HashMap();
// put the service in the HashMap
library.put('service', service);
// invoke the library with our context
service.invokeScript("SHARPTREE.LIB", library);

This form of invokeScript does not return a context, since one has been provided. As a matter of design, I would argue that it should for consistency, but IBM didn't ask me so it doesn't.

After the invoking the script, the script's functions are available on the provided context, so rewriting our previous example to call the add function would look like the following.

// import the Java HashMap
HashMap = Java.type('java.util.HashMap');
// create a new HashMap to use as the execution context
var library = new HashMap();
// put the service in the HashMap
library.put('service', service);
// invoke the library with our context
service.invokeScript("SHARPTREE.LIB", library);
// we can now invoke the desired function on our context
var result = library.add(1, 2);

With the example above, our invoked script will now have access to the service object during initial invocation, but if we want the service object to be available for other functions we will need to assign it to a local variable.

For example if we want to use the log_info function from the service object to log every time our add function is called we need to update our library script with the following.

var internalService = service;
function add(value1, value2) {
internalService.log_info("Add function called");
return value1 + value2;
}

Alternatively we can design our functions to require all the variables as parameters and not bother with the custom context, below is the add function rewritten to take the service object as a parameter.

function add(value1, value2, service) {
service.log_info("Add function called");
return value1 + value2;
}

The calling function can then be simplified to the following.

var result = service.invokeScript("SHARPTREE.LIB").add(1, 2, service);

The two forms achieve the same functionality and choosing one over the other is a matter of personal preference. I prefer the clarity of declaring what a function requires with its parameters and also prefer the compact syntax for invoking the function.

Calling Multiple Functions

As noted in the previous section, the invokeScript function returns the context of the invoked script. This can be used directly as in the previous example, or can be captured in a local variable and then invoked multiple times or to call other functions on the script.

Using the previous library example that contains the add function, let us now add a subtract function.

function add(value1, value2, service) {
service.log_info("Add function called");
return value1 + value2;
}
function subtract(value1, value2, service) {
service.log_info("Subtract function called");
return value1 - value2;
}

Our calling script can now be updated to capture the resulting context of calling invokeScript and then call both the add and subtract functions, as well as call either multiple times as shown below. In this example the context returned by invokeScript is assigned to mathLib and then the add and subtract functions are called.

var sharptreeLib = service.invokeScript("SHARPTREE.LIB");
var firstCall = sharptreeLib.add(1, 2, service);
var secondCall = sharptreeLib.add(3, 4, service);
var subtractionCall = sharptreeLib.subtract(4, 3, service);

Practical Example

Last week we looked at ensuring MboSet are closed properly, in case you didn't read it you can find it here. We ended up with the function below to close our MboSets.

MboSet = Java.type("psdi.mbo.MboSet");
function close(mboSet) {
// make sure the provided MboSet is defined and is an instance of psdi.mbo.MboSet
if (typeof mboSet !== 'undefined' and mboSet instanceof MboSet) {
try {
// release any pending Maximo transactions.
mboSet.cleanup();
} catch(error) {
// log the error, but there is nothing we can do to respond to it.
service.log_error(error);
}
try {
// remove any references and close the underlying JDBC ResultSet.
mboSet.close();
} catch(error) {
// log the error, but there is nothing we can do to respond to it.
service.log_error(error);
}
}
}

As you can see, that is a lot to include in every script. However if we were to move this function into our SHARPTREE.LIB script from before we can close our MboSet as shown below.

main();
function main() {
// define the MboSet outside the try/finally so it is available in the finally block
var mboSet;
try {
mboSet = service.getMboSet("WORKORDER", userInfo);
// perform operations with the MboSet.
} finally {
// close the MboSet using the function in our library script
service.invokeScript("SHARPTREE.LIB").close(mboSet);
}
}

If later we decided to extend the close method to include other types like ResultSets, Statements, or even Connections we can do so without having to update code in multiple scripts.

Conclusion

By extracting common functions to a library Automation Script we can simplify our other Automation Scripts while also making our code base easier to maintain. I have used simple examples here, but using this pattern you can compose complex and robust functionality in a manner that breaks down the functionality into manageable pieces.

In the time it took you to read this blog post...

You could have deployed Opqo, our game-changing mobile solution for Maximo.

Opqo is simple to acquire, simple to deploy and simple to use, with clear transparent monthly pricing that is flexible to your usage.