Workflow Primer – Part 2

2 Likes
In part 1 of this series, I discussed the basic technologies which underlie the IDM Workflow engine. This article will focus on the forms engine. Generally, this is the more fun part for me to work on because I see my feedback immediately.

Forms


 
The forms engine is represented in the workflow diagram as the start activity; it is what a user will see, or what a token-start-workflow or SOAP call will initiate. There is one step prior to the form, which is the pre-activity map which can be useful to perform some server side initialization of the form data before it is first rendered to the browser.

Naming


 
The foundation of this methodology is naming. By naming fields in programmatically useful ways, more of the implementation can be abstracted into libraries.

The primary way that this is instantiated is my rule #1 for form design:

Fields should be named to exactly match the DAL key for the attribute they represent.

The value of this will become clearer as we explore the script library functions and their implementations.

Which Scripts to Use?


 
There are four places where you can insert form script into your PRD:


  • Overview > Global Scripts

  • Form > Events

  • Form > Scripts

  • Form > Fields > Properties > Events



Overview > Global Scripts



In theory this should be the most global script. These scripts can be identified for use in forms and should affect all forms within the PRD. My experience with these has actually been inconsistent, so I tend to avoid this for forms use (I rely heavily on it for workflow, which will be discussed later).

Form > Events



This is a very important script. It is global to the form and can therefore be utilized in many important ways. These include:


  • Code to abstract the differences between browsers. Browsers who's javascript is missing support for certain features can be supplemented here. An example is that Internet Explorer does not have an "indexOf" method on arrays. This can be added here.

  • Initialize support - I have used this, for example, to initialize XML support in a form.

  • Global Variables - variables (and constants) declared here are global throughout the form and fields.



There are important limitations to note about this script. Information about individual fields is not available as this runs before the fields are instantiated. The IDVault object is also not instantiated here, so there is no access to identity vault data yet.

This is a good place to instantiate global objects in the form, such as JSON parsers and XML objects. One common use I make of this script in nearly all of my PRDs is to declare "Form = form" at the end of the script. This makes the form object available globally, so I do not need to pass it in in order to use it's methods within a script.

Form > Scripts

Most of my global scripts end up in here. They are conveniently available to use from the menus.

wp-2-image1

I try to write the functions so they can take the objects they need as their parameters, and to name those parameters to match the object name. So, often I can just pick the function and leave it unmodified.

Functions with the scripts have no direct access to the objects in a field or form. You MUST pass an instance of the object to have access to its methods. For example, if you want to write a function to return whether a user is a member of a group, you must pass the IDVault object to the function so that the code inside can run its methods. Such a function might look like this:

function isMemberOf(IDVault, dn)
{
var groups = IDVault.get(null, Form.getValue("recipient"), "group")
return (groups.indexOf(dn) != -1)
}


Since we need to access the Identity Vault, we must make the IDVault object one of the parameters. Though I don't have to, I name it the same so that we don't have to edit anything when we select that from the function browser in Designer. Note that I did not need to pass the "form" object because in my event onload script I include Form=form, so that object is available.

Form > Fields > Properties > Events



Each field can have zero or more events associated with it. These events can be based on user or program action (onclick, onchange, onfocus, onblur, etc.) or can be custom.

The fields have access to the form, field and IDVault objects. You may write sub-functions within any event handler, and can call scripts which pass these objects in.

My philosophy is that wherever possible, these event handlers should contain a minimal amount of code which invokes script functions. These functions should rely on the consistent naming of fields in order to do their business with minimal variations.

Events


 
The second part of my methodology is to use these scripts to publish events throughout the form. Fields which need to listen for event changes can then be configured easily to react.

The most common use of this is to allow a form to have a user selector, which when the user is selected fills in the fields with attribute values from the user.


  • In the recipient field, in an "onchange" event contains the code "publishEvent(field,event)". This just has to be pasted in from Designer with no changes.

  • Create a Field named "FirstName", and add an "onchange" event. Change the event name to onchange-recipient, and in the code add the code "fill(IDVault,event,field)".

  • Right click the field definition and paste it. Rename the pasted field to match their DAL key for their attribute, and update the Display Name (under the properties tab) to change the name displayed on the form.

  • Lather, rinse, repeat for as many text fields as you need.


The implementation of these methods is as follows:

/* republishes an event so other fields can use it in the format eventname-fieldname */
function publishEvent(field,event)
{
if (!undef(field.getValue()))
{
field.fireEvent(event.getEventName() "-" field.getName(),field.getValue())
}
}
function fill(IDVault,event,field)
{
var value=IDVault.get(null, event.getCustomData().toString(), "user", field.getName());
if (defaultDisabled.indexOf(field.getName()) != -1) field.enable();

if (!undef(value))
{
field.setValues(value)
}
else
{
field.setValues([""])
}


if (defaultDisabled.indexOf(field.getName()) != -1) enabled(field, Form.getValue("create"))
}


This works as follows: publishEvent() reads the field name and the field value, and fires an event named fieldname-eventname with the custom data of the field value. fill() gets the value published, looks at the field name and uses that to look up in the identity vault that attribute for the user selected in the recipient field.

Pre-Populating Fields


 
Another common case is a static list read from the DAL and place the value in a pull-down menu. This is a two part problem: 1) Populate the list, and 2) select an item from it.

Part 1 of this has two common solutions, read from a static list, or read from a directory query. Both of these options can be done in a single line of code:

IDVault.globalList(field.getName(), "SiteLocations");

-or-
IDVault.globalQuery(field.getName(), "Locations")


Coding-wise it is mildly more efficient if the attribute key matches either the list or the list key which matches the field name. Then you can load the values using code similar to this:
function initList(IDVault, field)
{
IDVault.globalList(field.getName(),field.getName());
}

Another possibility is that the list key matches the display label
function initList(IDVault, field, list)
{
IDVault.globalList(field.getName(),field.getLabel());
}

As far as selecting the value, I suggest a similar approach to the text field:
fillList(IDVault,event,field)

The implementation of that is as follows:
function fillList(IDVault,event,field) 
{
var values=IDVault.get(null, event.getCustomData().toString(), "user", field.getName());

if (!undef(values[0]))
{
try
{
field.select(values)
}
catch (e)
{
Form.showWarning(e.toString());
}
}
return values;
}


Why not use the start activity of the workflow?



Another option for populating fields is placing the code within the start activity of the workflow rather than in an onload event on the form. This has advantages and limitations.

Because the start activity is a back-end service, it has access to java classes and can do nearly anything. It also is quick, because it local to the web server and therefore does not require a back and forth conversation between the web server and the browser. All of this is good news, and if the start activity meets your needs, it is the better choice.

When using the start activity to populate your form, you cannot select the user for which the data in the form refers. You can load data from any user, but you can't choose the user until you reach the form. If this is purely a self-service form, and all the data is the logged in user's data, it works well.

The other difference is that the data loaded by the form is loaded at the security context of the logged in user. Data loaded in the start is loaded at the user application administrator context. This means that using that to construct a form could reveal data that a particular user is not entitled to see.

It usually makes sense to use the start menu to load data that the user might need but doesn't necessarily have rights to. For example, you can use the start menu to query for a list of roles where the user can choose a role they want to request. The personal data about the user might be loaded by the form query in the onload or in an onchange event.

How to populate a multi-valued attribute from the Start activity

In order to populate a multi-valued control from the start activity, you need to construct a java object. An example of how to do something of this nature is as follows:

function makeList()
{
list = new java.util.Vector()

list.add("Employee");
list.add("Contractor");
list.add("Student");
list.add("Intern");
return list;
}
MakeList();

Further discussion about object usage in the workflow engine will be discussed later.

Form Tips, Troubleshooting and Debugging techniques


 
While the basis of Designer is Eclipse, which is an excellent Integrated Development Environment (IDE), there are significant limitations when debugging PRD code. There is no mechanism for single stepping through code, so it is important to plan to work around these challenges.

Declare variables with var



Your initial reference to a variable should be declared with the keyword var. this indicates to the interpreter to construct a new copy of the variable and that it is a locally scoped copy. This will avoid potential conflicts with other scripts.

A notable exception would be in the Events ' onload; if you intend a variable to be global and persistent it must NOT be declared with the var keyword.

Flush frequently



All your interactions with the Identity Vault are through a service called "VDX" which uses the Directory Abstraction Layer to define what objects you have access to, what it's format and location is and other key data. If you need to create a list, create a query, add a new attribute, all of that is done in the DAL.

The DAL maintains a cache of configuration data from the Identity Vault and serves up all data based on that. This means if any change happens it is necessary to flush the cache to make sure it has taken effect.

wp-2-image2

Identifying comments at the top of each event code



When an error occurs within the form, you will be presented with a red message and a snippet of the source within the event handler which threw the exception. It can be extremely helpful if the first line of that code include the name of the field and the name of the event, it will speed up troubleshooting significantly.

Add your own debugging mode



If you truly embrace the event driven nature of this development technique, you can end up with one event starting many changes which each can spawn their own events, and so on; you end up having a whole series of cascading events. When a problem is in one of them it can be hard to locate them.

To handle this, I create a function to trace through the events. This is controlled by global debug flag so I don't have to necessarily remove this to deploy the solution. This can then be pasted in at strategic places through the code to help determine the sequence of events.

function eventTrace(field,event)
{
if (debug!="")
{
fieldValue = "";
if (!undef(field.getValue())) fieldValue = field.getValue();


var msg = "Event: " event.getEventName() "\n"
msg = "Field: " field.getName() "\n"
msg = "Value: \"" fieldValue "\"";

if (debug=="step") Form.alert(msg)
if (debug=="trace") Form.showDebugMsg(msg);
}
}


Testing in request vs. approval forms



Request forms and approval forms have the same capabilities and can be programmed in exactly the same manner. But as a practical matter, testing and debugging is easier within the request form. Approval forms require that you execute a request first then accept the approval. This extra step slows down the entire process to a crawl.

The suggested approach is to test all your logic in request forms, and then paste it into approval forms to do a final test. This will significantly speed the time to deliver a solution.

Conclusion


 
We have taken a look at some of my best practices for form automation. In the next part of this article, we will review some of the nuances of the workflow engine.

Labels:

How To-Best Practice
Comment List
Related
Recommended