JavaScript Functions

November 3, 2021

Introduction

When a JavaScript Automation Script is executed, it is executed from top to bottom. However unlike Python / Jython scripts, functions are elevated and can be called before they are declared. This provides the opportunity for JavaScript Automation Scripts to be better organized with a clearly defined execution path.

In this post we will look at how functions can be used to organize your scripts into logical blocks of functionality, not only to make them easier to read and maintain but to also avoid incidental or accidental behavior.

Functions

It is common to write JavaScript Automation scripts without functions, similar to an old BASIC program: Just a series of statements executing from top to bottom. However this approach quickly breaks down beyond the most simplistic scripts, and as many have discovered, these scripts become unreadable and very difficult to debug and manage.

Fortunately, JavaScript provides functions to organize your script into logical blocks of discrete functionality. Functions are declared with the function keyword followed by the function name, and may optionally have one or more parameters contained within parentheses. The body of the function follows, and is defined by statements contained within curly brackets.

Function names follow the same rules as variables, with letters, numbers, underscores, and dollar signs being valid. While there are no special meanings for function names within JavaScript, there is a useful convention of prefixing the names of private functions with an underscore. Since JavaScript does not provide a native mechanism for function visiblity or scope, this convention provides information to other developers about which functions are considered internal and should not be called externally.

Function names must be unique and unlike other languages, JavaScript does not support function overloading: Defining functions with the same name but different parameter signatures.

Functions are executed when something invokes or calls it using the function's name, providing any parameter values. All parameters defined by a function are considered optional, and will remain undefined if not provided.

A function will exit once its body has been evaluated or it reaches a return statement. The return statement can also return a value from the function as seen in exampleFunction3.

Example Functions

// No parameter function
function exampleFunction1() {
// statements go here.
}
// Function with two parameters
function exampleFunction2(parameter1, parameter2) {
// statements go here.
}
// Function with the private function name convention
function _exampleFunction2(parameter1, parameter2) {
// statements go here.
}
// Function with a return value.
function exampleFunction3(parameter1, parameter2) {
return parameter1 + parameter2;
}
// Invoking a function without defined parameters
exampleFunction1();
// Invoking a function with parameters
exampleFunction2("exampleValue1", "exampleValue2");
// Invoking a function without providing parameter values.
exampleFunction2();
// Invoking a function and obtaining the return value: result will be 3
var result = exampleFunction3(1, 2)

Functions and Automation Scripts

Example Script

Consider the following Automation Script where:

  • If the status is WAPPR the DESCRIPTION attribute is required,
  • If the status is APPR the WORKTYPE attribute is required,
  • If the status is CAN then do nothing,
  • The OWNER attribute is set to the current user name.
MboConstants = Java.type("psdi.mbo.MboConstants")
var mboStatus = mbo.getString("STATUS");
if (mboStatus === "CAN") {
// do nothing because in CAN status the record is read only
} else if (mboStatus === "WAPPR") {
mbo.setFieldFlag("DESCRIPTION", MboConstants.REQUIRED, true);
} else if (mboStatus === "APPR") {
mbo.setFieldFlag("WORKTYPE", MboConstants.REQUIRED, true);
}
mbo.setValue("OWNER", mbo.getUserInfo().getUserName());

Of course if the record is in CAN status then the work order is read only and OWNER attribute cannot be set. However, because the script evaluates top to bottom in its entirety, the final statement is executed regardless of the status checks.

The options to avoid this are either setting the OWNER value in each status check or by adding another check at the end of the script:

MboConstants = Java.type("psdi.mbo.MboConstants")
var mboStatus = mbo.getString("STATUS");
if (mboStatus === "CAN") {
//do nothing because in CAN status the record is read only
} else if (mboStatus === "WAPPR") {
mbo.setFieldFlag("DESCRIPTION", MboConstants.REQUIRED, true);
} else if (mboStatus === "APPR") {
mbo.setFieldFlag("WORKTYPE", MboConstants.REQUIRED, true);
}
if (mboStatus !== "CAN") {
mbo.setValue("OWNER", mbo.getUserInfo().getUserName());
}

A clearer way to achieve this is by wrapping this logic in a function and using the return statement to explicitly terminate the code execution path and return to the calling statement.

MboConstants = Java.type("psdi.mbo.MboConstants")
setRequiredFields();
function setRequiredFields() {
var mboStatus = mbo.getString("STATUS");
if (mboStatus === "CAN") {
// do nothing because in CAN status the record is read only
return;
} else if (mboStatus === "WAPPR") {
mbo.setFieldFlag("DESCRIPTION", MboConstants.REQUIRED, true);
} else if (mboStatus === "APPR") {
mbo.setFieldFlag("WORKTYPE", MboConstants.REQUIRED, true);
}
mbo.setValue("OWNER", mbo.getUserInfo().getUserName());
}

Not only does this allow for the function to return and stop further evaluation if the status is CAN, but it also scopes the variable mboStatus to this function and helps avoid variable name conflicts. This also encapsulates the functionality of setting required fields and makes the code easier to read.

However, as this script becomes more complex, the number of function calls may increase as well. The example below illustrates adding another function. In a real world script the list of function calls can quickly become complicated and may evolve to have conditional logic about which functions will be called based on record state.

MboConstants = Java.type("psdi.mbo.MboConstants")
setRequiredFieldsAndOwner();
setReadOnlyFields();
function setRequiredFieldsAndOwner() {
var mboStatus = mbo.getString("STATUS");
if (mboStatus === "CAN") {
// do nothing because in CAN status the record is read only
return;
} else if (mboStatus === "WAPPR") {
mbo.setFieldFlag("DESCRIPTION", MboConstants.REQUIRED, true);
} else if (mboStatus === "APPR") {
mbo.setFieldFlag("WORKTYPE", MboConstants.REQUIRED, true);
}
mbo.setValue("OWNER", mbo.getUserInfo().getUserName());
}
function setReadOnlyFields() {
var mboStatus = mbo.getString("STATUS");
if (mboStatus === "CAN") {
//do nothing because in CAN status the record is read only
return;
} else if (mboStatus === "WAPPR") {
mbo.setFieldFlag("DESCRIPTION", MboConstants.READONLY, false);
} else if (mboStatus === "APPR") {
mbo.setFieldFlag("WORKTYPE", MboConstants.READONLY, false);
}
}

Recommendation

In the C language and its derivatives such as C++ and Java there is a special function named main that acts as the entry point for a program. We have found that implementing this convention in Automation Scripts provides a clear entry point to the script and makes it much easier to follow the execution path. Using the previous example, we have restructured it with a main method, allowing us to evaluate for the CAN status once and clearly halt execution if the condition is met.

MboConstants = Java.type("psdi.mbo.MboConstants");
main();
function main() {
if (mbo.getString("STATUS") === "CAN") {
return;
} else {
setRequiredFieldsAndOwner();
setReadOnlyFields();
}
}
function setRequiredFieldsAndOwner() {
var mboStatus = mbo.getString("STATUS");
if (mboStatus === "WAPPR") {
mbo.setFieldFlag("DESCRIPTION", MboConstants.REQUIRED, true);
} else if (mboStatus === "APPR") {
mbo.setFieldFlag("WORKTYPE", MboConstants.REQUIRED, true);
}
mbo.setValue("OWNER", mbo.getUserInfo().getUserName());
}
function setReadOnlyFields() {
var mboStatus = mbo.getString("STATUS");
if (mboStatus === "WAPPR") {
mbo.setFieldFlag("DESCRIPTION", MboConstants.READONLY, false);
} else if (mboStatus === "APPR") {
mbo.setFieldFlag("WORKTYPE", MboConstants.READONLY, false);
}
}

Conclusion

By organizing your code into functions you can provide clear execution logic and avoid accidental fall through execution. By using the main function convention the entry point and execution path for the script is clearly defined and easy to follow, which reduces errors and simplifies troubleshooting.

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.