Previous     Contents     Index     DocHome     Next     
iPlanet Process Manager 6.0 (SP2) Process Builder's Guide



Chapter 11   Advanced Techniques for Scripting


This chapter describes how to write your own scripts for use with Process Builder.

This chapter has the following sections:



Introduction

When writing a Process Manager script, consider the types of information your script will use. Process Manager scripts can use the following sources of information:

  • Information about the process in progress.

    This data is stored in Process Manager's database, and is available through the processInstance object. This information includes data such as the date the process instance was created and the current value of fields such as the specialDiscount field.

  • Information about the work item in progress.

    Information about the current work item is available through the workItem object. This information includes data such as the user to whom the work item is assigned.

  • Information about users.

    This information is stored in the Directory Server, and is available through the corporateDirectory object, discussed in Getting Information about Users and their Attributes. This data includes information about users, such as the assignee's address or the name of their manager.

  • Items stored in the content store.

    Scripts can access items stored in the content store by using the contentStore object (discussed in Accessing the Content Store).



Getting Information about the Current Process

Process Manager provides two JavaScript objects, processInstance and workItem, that scripts can use to access information about the process in progress:

  • processInstance holds information about the process instance in general, such as its creation date and the current values of any data fields. Scripts can use the global function getProcessInstance() to get the object. Scripts can then call methods on the processInstance to get and set data relevant to the process instance.

  • workItem holds information about the current work item, such as its assignee and its expiration date. Scripts can use the global function getWorkItem() to get the object. Scripts can then call methods on the workItem object to get and set data relevant to the work item.

See Appendix , JavaScript API Reference for details on these methods.


Getting and Setting Data Field Values

When a user submits a form for a work item, the value of each data field in the form is put into the corresponding data field in Process Manager's database. For example, if the user sets the value of the field documentName to Jo Writer then when the form is submitted the field documentName in Process Manager's database gets the value "Jo Writer". (You could think of the field in the form as being a window to the field in the database.)

There are several ways to verify the data that users enter in the form fields, as discussed in Verifying Form Input.

Scripts can use the following methods on the processInstance object to get and set data field values:

  • getData (fieldName)

    This method returns the value of the named field.

  • setData (fieldName, fieldValue)

    This method sets the value of the named field to the given value. This method is particularly useful for setting values in automation scripts.

The following function is an example of a completion script that gets and sets field values:



function setOrderPrice () {
   // Get the process instance.
   var pi = getProcessInstance ();
   // Get the value of the item_price field.
   var price = pi.getData ("item_price");
   // Get the value of the number_ordered field.
   var numOrdered = pi.getData ("number_ordered");
   // Calculate the total price.
   var totalPrice = price * numOrdered;
   // Put the total price in the order_price field.
   pi.setData ("order_price", totalPrice);
   // Successful completion scripts return true.
   return true;
}


Getting Data Field Values in Decision Point and Automation Script Transitions

The value of a condition for a decision point or an automated script can be any expression that returns true or false.

These expressions can use a shorthand notation to refer to field values by simply using the name of the field. For example:

item_Price < 100

is shorthand for

getProcessInstance().getData("item_price") < 100

This expression evaluates to true if the value of the item_price field is less than 100, otherwise it evaluates to false.

Note that only scripts used in conditions can use this shorthand for accessing data fields.



Getting Information about Users and their Attributes



Scripts can use the global function getCorporateDirectory() to get an object that represents the corporate directory in the Directory Server that Process Manager uses.

The Directory Server contains a database of users and their attributes, such as their distinguished name, their common name, their phone number, their address, their email address, and so on. Given the corporateDirectory object, scripts can get information about the users in the corporate directory.


Finding Users and Accessing their Attributes

The corporateDirectory object has three methods that allow scripts to search for users in the corporate directory:

  • getUserByCN (userCN)

  • getUserByDN (userDN)

  • getUserById (userID)

These methods all return a JavaScript object that represents the user if they can be found; otherwise the methods return null. The object has variables for all the user's attributes. See Appendix , JavaScript API Reference for details.

Scripts can also use the following two methods on the processInstance object to get user attributes from the Directory Server:

  • getCreatorUser() returns a JavaScript object containing the attributes of the user that created the process instance.

  • getRoleUser(roleName) returns a JavaScript object containing the attributes of the user assigned to the given role.

For example, the following assignment script assigns the work item to the user whose user id is the value of the peerReviewerID attribute of the person who created the process instance.



assignToPeerReviewer () {
   var pi = getProcessInstance ();
   var creator = pi.getCreatorUser ();
   // Assignment scripts return an array of distinguished names.
   return new Array (creator.peerReviewerID);
}


Note that this script works only if the peerReviewerID attribute is defined in the Directory Server and is initialized for the creator user.


Modifying User Attributes

The corporateDirectory object has the following methods for adding, replacing, or deleting user attribute values in the Directory Server:

  • modifyUserById (userID, attrName, attrValue, operation)

  • modifyUserByCN (userCN, attrName, attrValue, operation)

  • modifyUserByDN (userDN, attrName, attrValue, operation)

These methods specify the user either by ID or common name as appropriate. The attrName parameter is the name of the attribute to be modified. If operation is ADD, the value attrValue is added to any existing values. If operation is REPLACE, all existing values are replace by attrValue. If operation is DELETE, then attrValue is deleted from the attribute. The functions return true if the operation is successful, otherwise they return false.

For example, the following code puts the value nikki@company.com to the mail attribute of the user whose common name is Nikki Beckwell:

var corpdir = getCorporateDirectory();

corpdir.modifyUserByCN ("Nikki Beckwell", "mail", "nikki@company.com", "REPLACE");

Although scripts can modify attribute values, they cannot create attributes that have not already been defined in the Directory Server schema.


Adding and Deleting Users

The corporateDirectory object has the following methods for adding and deleting users to the corporate directory:

  • deleteUserByCN (userCN)

  • deleteUserByDN (userDN)

  • deleteUserById (userID)

  • addUser (userDN, attributes, objectClasses)

See Appendix AJavaScript API Reference for details.



Accessing the Content Store



Scripts can use the global function getContentStore() to get a contentStore connected to the content store for the process.

The following global function is useful for constructing a file name when you want to add an item to the object store:

  • getBaseForFileName() returns the folder name that the current process instance uses to contain its stored content.

For more information about methods on the content store, see Appendix , JavaScript API Reference


Example of Accessing a Stored Item

For an example of accessing the content store, consider a process that writers use for submitting documents for review. Before starting the process, the writer must create a file containing a synopsis of the document.

The entry point form contains a File data field named docDescription. The writer selects a file containing the document synopsis and uploads it by pressing the File icon in the form. The next activity after the entry point is an automated activity that uses the following automation script. This script gets the document synopsis by getting the content of the file that the writer selected. The script then puts the synopsis into the docSynopsis TextArea datafield. The intention here is that the next form in the process displays the synopsis to the users assigned to review the document.



function getDocDescription (){
   // Use the getProcessInstance() global function to get
   // a reference
   // to the process instance.
   var pi = getProcessInstance();

   // Retrieve the URL where the file containing the document
   // description is stored in the content store. This was set
   // when the writer selected a File containing a document
   // description.
   var descriptionURL = pi.getData("docDescription");

   // Get a reference to the content store service through the
   // global function getContentStore().
   var contentStore = getContentStore();

   // Get the content of the file.
   var docDescription = contentStore.getContent(descriptionURL);

   // Put the document description in a text field
   // called docSynopsis.   
   pi.setData ("docSynopsis", docDescription);
   // Return true to proceed to the next activty.
   return true;
}


Note. This script requires that the content store URL defined for the application is a valid URL, such as:

http://pm.company.com/pmstore

If the content store URL is not defined correctly, the store() method will not work.


Storing Files in the Content Store

Each Process Engine uses a series of directories named part0, part1, part2 and so on to contain items in the object store. Each process instance has a unique identity, such as pi0001. Each process instance uses a partn subdirectory to store all its content. For example, all the stored files associated with process instance pi0001 would be stored in PM_root/part0/pi0001. When part0 gets full, the Process Engine starts using part1, and so on.

When a script needs to save content to the content store, it can use the getBaseForFileName() function to get the pathname for the content store directory for the current process instance. The script can then use the store() method on the contentStore object to save content to a file in the correct content store directory for the current process instance.

For example, the following automation script code creates a path name for the file myNewFile.html in the content store, then saves some content to a new file in the content store.



function storeSomething () {
   var pi = getProcessInstance();
   // Create a pathname for a new file in the content store.
   var pi = getProcessInstance ();
   var pid = pi.getInstanceId();
   var contentStorePath = getBaseForFileName (pid);
   var newFileName = contentStorePath + "myNewFile.html";

   // Create a string containing some content to be saved to the file.
   var newContent = "Do something that generates a content string here";

   // Store the content in the file myNewFile.html.
   var myStore = getContentStore ();
   myStore.store (newContent, newFileName);
   // Return true to proceed to the next work item
   return true;
}





Logging Error and Informational Messages



Process Manager provides several global functions for providing messages. Scripts can use these functions to log error messages, to add entries to the history log, and to add informational messages for the user when a script fails.

These functions are:

  • logErrorMsg()

  • logInfoMsg()

  • logSecurityMsg()

  • logHistoryMsg()

For more information about these global functions, see Appendix , JavaScript API Reference



Verifying Form Input



This section has the following subsection:

When you create a form in Process Builder, you place data fields on it. If the data field is in edit mode, it displays the current value, if any, and allows the user to modify the value. If a data field is in view mode, it displays the value of the field but does not allow the user to change it.

When the user submits the form by pressing an action button, the values in the data fields in the form are put in the fields in the process instance's table in the database. By default, Process Manager verifies that the data entered in each field is the correct type, and if there are obvious errors, it rejects the work item or entry point and displays a warning dialog box. For example, if the user enters the string value "whenever" into a data field whose type is Date, Process Manager catches the error and rejects the work item. (Note however that the Date verification is not very strict, if the user enters 6666/7777/8 as a Date, Process Manager accepts it.)

There will be times when your form needs to perform additional data verification, such as checking that a number falls within a certain range of numbers. Process Manager provides the following way to verify that users enter valid data for a field:

  • use client-side JavaScript scripts to define onClick or onValueChange properties for those data fields that have them, or to define onSubmitForm scripts.

  • use onCompletion scripts to perform data type verification.

Using client-side scripts to check data field values allows you to write scripts that give the user a chance to fix the values before the form is submitted.

If the data verification occurs in a completion script, then if the script fails the result is that the form disappears and the process is routed to an exception node.


Verifying Form Input with Client-Side JavaScript

Process Builder provides two ways to write client-side scripts to verify form input:

  • using an embedded script that defines an onSubmitForm function that gets invoked when the form is submitted.

  • using onValueChange and onClick event handlers for specific form elements.

For every data field represented in a form, the form contains a form element that has exactly the same name as the data field. Client-side scripts can access data field values as they are currently displayed in the form. The scripts do this by accessing form elements that have the same name as the data field.

If the form contains an embedded client-side script that defines an onSubmitForm function, that function is invoked when the user presses an action button on the form. You can define an onSubmitForm function to check values of form elements and offer the user a chance to enter new values in cases where the current values are invalid. If the onSubmitForm function returns false, the form is not submitted. See the section onSubmitForm Example for a coded example of an onSubmitForm function.

You can also define client-side scripts to check the value of individual form elements. Some data fields have an onClick or onValueChange property. The value of this property is a JavaScript expression. These properties correspond to event handlers in the associated form elements. For information about form elements, see the "Forms" chapter in the HTML Tag Reference at:

http://developer.netscape.com/docs/manuals/htmlguid/index.htm

The onClick script is fired when the form element is clicked. For elements whose value is changed by clicking, such as radio buttons, the onValueChange script behaves just like an onClick script, and is fired whenever the user clicks the form element. For other elements, such as text fields, the onValueChange script is fired when the form element loses focus after its value has changed, which usually happens when the user presses the return key or clicks elsewhere in the form. To see exactly which data fields have onClick and onValueChange scripts, see the properties for the different kinds of data fields in Process Builder.

You can define onClick, onValueChange, and onSubmitForm scripts that display an alert box or a confirm box to warn the user that an invalid value has been entered. Use the alert() function to display an alert box, and use the confirm() function to display a confirm box.

To obtain information about JavaScript errors, enter the URL javascript: in your version of Netscape Communicator.


Event Handler Example

The following onValueChange script for a text field checks if the value of the text field is a number. If the value is not a number, it displays a dialog box with a warning and resets the page count value to 100 in case the user ignores the warning. If the value is a number, it does nothing. (Note that in event handlers, such as in client-side scripts, you must use single quotes instead of double quotes.)


var pageCountNum = parseInt (value);
if (isNaN (pageCountNum)) {
   alert ('You must enter a number as the page count');
   value = 100;
}

Figure 11-1 shows the definition of this onValueChange event handler in the Inspector window in Process Builder.

Figure 11-1    A definition for an onValueChange event handler


Figure 11-2 shows a sample alert window in Process Express:

Figure 11-2    A sample alert window in Process Express


This script can be used directly as the value of the onValueChange property of the text field. The script uses parseInt() to convert the value of the text field from a string to a number. The value argument to the parseInt function is the name of a property of the text field. Since this script is used directly in the text field object, there is no need to say this.value, although the script would also work if written as:

var pageCountNum = parseInt (this.value);

// etc...

For information about the parseInt() method, see Chapter 13, Global Functions in the JavaScript reference at:

http://developer.netscape.com/docs/manuals/communicator/jsref/


onSubmitForm Example

The following example shows an embedded client-side script that defines an onSubmitForm function. In this case, the script checks that the value entered in the daysForReview form element is a number. If not, the script displays a warning dialog box, sets the value of the daysForReview form element to 10, and returns false to give the user a chance to change the value and submit the form again.


function onSubmitForm () {
   var daysForReview = document.forms[0].daysForReview.value;
   var reviewPeriodNum = parseInt (daysForReview);
   if (isNaN (reviewPeriodNum)) {
         alert ('Enter a number of days in the review period');
         document.forms[0].daysForReview.value = 10;
         return false;
   }
   else return true;
}

Figure 11-3 shows an embedded client-side script that defines an onSubmitForm function in Process Builder:

Figure 11-3    A script that defines an onSubmitForm function



Verifying Form Data in Completion Scripts

When the user presses an action button to submit a form, the values of the data fields on the form are installed into the global processInstance object. When the completion script runs, it can access data field values by using the getData() method of the global processInstance object. However, if the completion script fails, the newly installed values are removed from the process instance, the previous values are re-installed and the process is routed to an exception node.



Initializing and Shutting Down Applications



When defining a process definition, you can write initialisation and shutdown scripts for the application. The initialisation script is invoked when the application first starts, and the shutdown script is invoked when the application is shut down. Note that the initialization and shutdown scripts are not invoked each time a process instance starts or finishes, but only when the application starts or finishes. The application lifespan (start to finish) is defined within the scope of each Java engine running within the application server. Therefore, the initialization scripts are invoked on a per-engine basis.

Initialization scripts and shutdown script are not associated with individual process instances, therefore they cannot use processInstance or workItem objects. They can however use the methods on the contentStore object. For example, the initialisation script could use the store() method on the contentStore object to create a file that can be accessed by all process instances in the application.

You can use the initialization script to perform tasks that need to be done once in the lifetime of the application, such as creating files or initiating connections to be shared by all process instances.

The shutdown script should close any connections that were opened in the initialization script. Other uses for a shutdown script include cleaning up external database tables, sending email to the administrator, releasing other resources, and performing any tasks that need to be done when the application is shut down.



Debugging Hints



To check for syntax errors in your scripts, enter javascript: as your URL. In addition, this section provides hints on debugging and troubleshooting scripts. It has the following subsections:


Displaying the Progress of a Script

There are several global functions for printing messages that are very useful for debugging scripts. The logErrorMsg() function adds an entry to the error log. The logInfoMsg() function adds an entry to the informational log. The logSecurityMsg() function adds an entry to the security log.

To view these logs, go to the Process Administrator page (Administrator.apm). For example, if Process Manager is installed at pm.company.com, go to:

pm.company.com/Administrator.apm/

This page displays Process Administrator. Select the Applications tab (if it is not already selected) to see the list of applications. Select View Logs from the pop up menu for the application of interest, then press the Apply button. You may need to enter your user name and password to gain access to Process Manager and the Enterprise Server.

When the View Logs Server Selector window appears, select the desired cluster, then press the View Log File button. In the View Application Logs window that appears, you can select the Error, Security, or Information log. After making your selection, press the View Log button.


Testing Expiration Setter and Handler Scripts

When Process Express displays work items in the work item list, it shows the expiration date for each work item. To test an expiration setter script, simply deploy the application and test it out. Check the expiration date for the relevant work item in the work item list in Process Express. For example, Figure 11-4 shows the due date in Process Express.

Figure 11-4    Testing a due date


At regular intervals (about once every minute) Process Manager tests for expired work items by comparing the expiration date and time of every work item to the current time. If the expiration time has passed for any work item, Process Manager invokes the work item's expiration handler script if it has one.

If your expiration handler re-assigns the work item, you should view the details and history for the process instance to check if the re-assignment worked in test mode. The title of the process does not change in test mode even if the assignee changes.



Sample Scripts



This section gives an example of the following kinds of scripts:


Assignment Script

The following assignment script assumes that the process instance has data fields reviewer1 and reviewer2 that contain the user IDs for two reviewers. If one or both of the reviewer1 and reviewer2 fields contain user IDs for valid users, the work item is assigned to the valid named reviewers. Otherwise it is assigned to the process instance creator.

The default value for the reviewer1 and reviewer2 fields is an empty string. The script checks that the reviewers exist as users in the corporate directory, and if so, assigns the work item to them. If the array does not contain valid user IDS, the function assigns the work item to the creator of the process instance.


function assignToNamedReviewers ()
{
   // Get the process instance and corporate directory
   var pi = getProcessInstance ();
   var corp = getCorporateDirectory ();

   // Create an array to contain the assignees.
   var assigneeArray = new Array ();

   // Get the user ids for reviewer1 and reviewer2.
   // Default values of revewer1 and reviewer2 data fields
   // are empty strings
   var reviewer1 = pi.getData ("reviewer1");
   var reviewer2 = pi.getData ("reviewer2");

   // Check that reviewer1 exists in the corporate directory
   if (reviewer1 != "") {
         reviewer1 = corp.getUserByID (reviewer1);
         if (reviewer1 != null) {
            assigneeArray.push (reviewer1);
         }
   }

   // Do the same thing for reviewer2.
   if (reviewer2 != "") {
         reviewer2 = corp.getUserByID (reviewer2);
         if (reviewer2 != null) {
            assigneeArray.push (reviewer2);
         }
   }

   // If the assignee array is not empty
   // return the assignee array otherwise return an array
   // of the creator of the process instance.
   if (assigneeArray.length != 0) {
         return assigneeArray;
   }
   else {
         return new Array (pi.getCreatorUser());
   }
}




Expiration Setter Script

This expiration setter script gets the value of the pageCount field and uses it to determine whether reviewers need a long or short review period. If the page count is greater than 100, the review period is long (14 days), otherwise the page count is short (7 days).


function setEndOfReviewPeriod(){
   // Get the process instance.
   var pi = getProcessInstance();

   // Get the page count.
   var pageCount = pi.getData ("pageCount");

   // The review period is based on the page count.
   // For pages < 100, expire in 7 days.
   // For pages > 100, expire in 14 days.
   var reviewPeriod;
   if (pageCount > 100) {
    reviewPeriod = 14;
   }
   else {
    reviewPeriod = 7;}

   var now = new Date();
   var nowTime = now.getTime(); //UNIX epoch time
   var expTime = nowTime + (reviewPeriod * 24 * 60 * 60 * 1000);
   return new Date(expTime);
}



Expiration Handler Script

This expiration handler script reassigns the work item to the creator.


function reviewPeriodExpired(){

   // Get the process instance and the work item.
   var pi = getProcessInstance ();
   var wi = getWorkItem();

   // Re-assign the work item to the creator
   wi.removeAssignees();
   wi.addUserAssignee(pi.getCreatorUser());

   return true;
}


Completion Script

The following completion script checks that the value of the pageCount data field is a number between 1 and 3000 inclusive. If the pageCount is too high, too low, or is not a number, the completion script logs an error message and returns false. The process instance is then routed to the appropriate exception node.


function checkPageCount(){
   var pi = getProcessInstance ();
   var pageCount = pi.getData("pageCount");

   // The standard JavaScript function parseInt gets an integer
   // from a string if it has one, otherwise returns 0.
   // For example, parseInt("33hello") returns 33.
   // If the first character cannot be converted to a number,
   // parseInt returns "NaN".
   var pageCountNum = parseInt(pageCount);

   // The standard JavaScript function isNaN
   // tests if a value is not a number.
   if ((isNaN (pageCountNum)) ||
       (pageCountNum < 1) || (pageCountNum > 3000)) {
         var e = new Object();
         e.exceptionString = "The page count was" + pageCount +
            " This value is invalid. " +
            "The page count must be a number " +
            "between 1 and 3000 inclusive.";
         logErrorMsg ("INVALID_PAGE_COUNT_ERROR", e);
         };
         return false;
   }
   else return true;
}


Automation Script

This script updates a link to a newly submitted document in a file that lists all documents available for review.

This script could be used as an automation script in a process that enables writers to submit documents for review. In the entry point form, the writer uses a File attachment data field called docFileName to attach the document. Many different writers can use the application to submit documents. The file docList.html contains an active link for every document that has been submitted with this application. The file docList.html lives in the top level of the content store so that all process instances in the application can access it.

Two files are involved in the document list. The file docList.html is an HTML-formatted list of the documents with an active link for each document. The file showList.html contains opening HTML tags, the list of documents (which is the same as docList.html), and closing HTML tags. This script uses two files so that it can add the closing HTML tags after adding the information for the newly submitted document.


function updateDocReviewWebPage(){
// Get the process instance.
var pi = getProcessInstance();

// docList.html and showList.html are stored at the
// top level in the content store.

// Get the root URL for the content store.
var myStore = getContentStore();
var storePath = myStore.getRootURL();

// Get the full pathnames to docList.html and showList.html.
var docListPath = storePath + "docList.html";
var showListPath = storePath + "showList.html";

// Get the contents of the doc list.
var docList = myStore.getContent(docListPath);

// Get the pathname to the doc that was submitted for review.
var docURL = pi.getData("docFileName");

// Get info about the newly submitted document.
var docname = pi.getData("docname");
var writer = pi.getData("writername");
var writerComments = pi.getData("writerComment");

// Add a link to the newly submitted doc in the doc list.
docList += "<A HREF=" + docURL + ">";
docList += "<H2><FONT COLOR='#55CCAA'>" + docname +" </H2></FONT></A>";
docList += "<P>Author: " + writer + "</P>";
docList += "<P>Author comments: </P>";
docList += "<BLOCKQUOTE>" + writerComments + "</BLOCKQUOTE><HR>";

// Store the doclist back into docList.html
myStore.store (docList, docListPath);

// Create the final content, which contains the heading tags,
// the doc list, and the closing tags.
var finalContent = "<HTML><HEAD><TITLE>" +
   "Documents available for review </TITLE></HEAD>";

finalContent += "<BODY><CENTER><H1>" +
   "Documents Available for Review" +
   "</H1></CENTER><P>" +
   "We encourage feedback on these docs from everyone.</P>" +
   docList + "</BODY></HTML>";

myStore.store (finalContent, showListPath);

return true;
}


Figure 11-5 illustrates the showList.html web page. This page is updated each time a document is submitted for review.

Figure 11-5    A web page that is updated automatically



Previous     Contents     Index     DocHome     Next     
Copyright © 2001 Sun Microsystems, Inc. Some preexisting portions Copyright © 2000 Netscape Communications Corp. All rights reserved.

Last Updated March 13, 2001