ShowTable of Contents
Table of contents |
Previous Page |
Next Page
This page explains how a workflow can be implemented into a web application built with XPages. Starting from an Notes client workflow application, this page will demonstrate how most of the existing LotusScript can be used within the web workflow. Also, an alternative solution using Server Side JavaScript will be included.
Understanding the existing Notes client workflow
Before getting started with adding the workflow into the XPage application, we need to have a look at the existing Notes client workflow to fully understand how it works.
Workflow setup
In the Document form, the Notes client workflow steps are triggered by the following action buttons:
- Submit for review (author)
- My review is complete (approver)
- Clear review cycle (author)
Additionally, a
scheduled agent called "Process Late Reviews" processes pending reviews after the specified time limit
The action buttons contain @Formulas which
- perform initial field validation
- set field values
- call a document save
The form's QuerySave event
- performs field validation (overlap with action buttons)
- sends e-mail to workflow actors (serial or parallel)
- updates fields (status, currenteditor)
QuerySave
The code in the form's QuerySave event can be divided basically into 3 parts:
The first part identifies which action has been clicked and defines, in combination with the status, whether the execution should continue or not.
The second part performs field validations.
The third part, on the very bottom, performs the actual backend processing by calling the functions SendToNext and SendToAll in the Script Library "SubmitForReview".
Copy after submit
After submitting the document for review, a copy of the document, called the "original copy", is created and saved as a response document. This document cannot be edited any more. This is controlled by a QueryOpen event script, and not by author access.
Conclusion
This workflow is not very well structured. It is has rather organically/historically grown.
In the particular case of the Document Library, it would probably be more efficient to rewrite the workflow in order to web enable the application. This would make the code more understandable + maintainable.
A well-written notes client workflow would
- do only back-end processing in QuerySave scripts, and not front end calls (source.fieldgettext)
- make use of a status field to capture the document status and an action field/variable to capture the user action. The formula for a workflow action button would be for example:
FIELD Action = "submit"
@PostedCommand([FileSave])
- have a well-structured query save script with a case statement per action:
Select case action
case "submit":
....
case "approve:"
...
In order to make this exercise more relevant, we will focus on the back-end processing part of the workflow.
There is no real need to go into detail about the field validations part because
- this has already been covered on the previous page. For example, the test if the reviewer is not the current user. In the Notes client application, this test was done at submit. In our XPage application, the test is executed as soon as the reviewer name is added to the list (in the Add) button, which gives a better user experience.
- the Combo Box fields on the XPage cannot be blank, hence there's no need to test on that.
On this page, we will focus on implementing the
Submit for review action. This action performs the most processing. And once the framework for workflow has been set up for this action, the other two actions can be added easily later on.

Create a workflow action in the form
We will start with creating a "Submit for review" action button in the form:
Between the Save and Edit buttons, add a new Button control with the following properties:
Name: buttonSubmit
Label: Submit for Review
Button Type: Submit
Style Class: lotusFormButton
We will add coding behind the button later.
Workflow processing with a LotusScript agent
In order to reuse the existing LotusScript code, the QuerySave event code needs to be placed in an agent, which then will be placed into the postSaveDocument event of the data source of the XPage.
Create the agent
Create a new agent with the name (xpFormDocumentPostSave) and alias xpFormDocumentPostSave:
In the agent properties, choose "Agent list selection" as trigger and "None" as target.
Now we need to get add the LotusScript code of the Document form's QuerySave event into this agent, and adapt it for use as a backend agent. The main difference will be the way the current backend document is accessed.
Accessing the current document with an agent that is called from an XPage
|
|---|
In traditional Notes client applications, the Notesdocument object that represents the current document is accessed via uidoc.document.
In traditional Domino Web applications, we use session.documentcontext
The approach for XPages is somewhat different: the NoteID of the document that is calling the agent can be accessed via a property of the NotesAgent: ParameterDocId.
With the NoteID, the document can then be found in the database with GetDocumentById.
This implies that the document needs to be saved in the database before it can be accessed this way.
Dim doc As NotesDocument
Dim sNoteID As String
Dim agent As NotesAgent
Set agent = session.CurrentAgent
sNoteID = agent.ParameterDocid
Set db = session.CurrentDatabase
Set doc = db.GetDocumentById(sNoteID)
|
The above code gives a handle to the current document. We will use this in combination with the backend processing part of the Document form's QuerySave agent:
The functions
SendToNext and
SendToAll are defined in the
Script Library "SubmitForReview". The QuerySave agent code suggests that these functions return a boolean with value True if the processing was successful and False if it failed.
Actually, these functions always return true: the beginning of the function SendToNext contains a statement that sets its value to true:
The function further contains no statement that returns False in case of an exception.
The same applies to SendToAll.
If, for example, in the Notes client application, you enter an inexisting reviewer name and submit the document for review, a messagebox will appear and say that no e-mail could be sent (= caused by the error handler SerMailError in the sub SerSendMail), but the script will continue execution and the document will be saved and it will have the status "submitted for review".
In other words, the If-statements for SendToNext and SendToAll can be removed in the QuerySave script.
The resulting code for the agent would then be:
Option Public
Option Declare
Use "SubmitForReview"
Sub Initialize()
Dim session As New NotesSession
Dim sNoteID As String
Dim agent As NotesAgent
Set agent = session.Currentagent
sNoteID = agent.Parameterdocid
Set db = session.currentdatabase
Set note = db.Getdocumentbyid(sNoteID)
If Not note Is Nothing Then
If note.ReviewType(0)="1" Then
Call SendToNext
Else
Call SendToAll
End If
Call note.Save(True, False, False)
End If
End Sub
Because the functions SendToNext and SendToAll in the SubmitForReview script library only used backend LotusScript classes, they don't need any changes in order to have them working with the XPages.
Call the agent from the XPage
Next step is to call the agent from XPage. As indicated above, the NoteID of the current document needs to be passed to the agent. The agent will thus need to be called after the document was saved.
Open the XPage formDocument, and in the Events view, add the following Server Side JavaScript in the postSaveDocument event of the data source dominoDoc:
var id = dominoDoc.getDocument().getNoteID();
var ag = database.getAgent("xpFormDocumentPostSave")
ag.run(id)

Pass the action to the agent
Finally, we need to ensure that the agent only runs when the user clicks on the Submit for Review button, and not on a simple Save. This will be done by setting a scoped variable in the Submit for Review button, and reading this variable in the postSaveDocument event of dominoDoc:
Select the Submit for Review button in ccFormDocument and in the Events view, add a simple action to the onclick event:
Add a second simple action: Open Page: Previous Page.
Return to the postSaveDocument event of dominoDoc (Events of the XPage formDocument), and add a condition to only run the agent if the scoped variable has the value "submit":
if(viewScope.actionPerformed =="submit"){
var id = dominoDoc.getDocument().getNoteID();
var ag = database.getAgent("xpFormDocumentPostSave")
ag.run(id)}
When you now create a Library Document in the web browser and submit it for review, the agent code has executed: a copy of the document has been created ("Original copy"), the value of the field "status" has changed to 2, and an e-mail has been sent to the first reviewer.
Display the Submit for Review button conditionally
The Submit for Review button should only be visible if
- the document is a new document
- the status = 1 AND the current user is the author AND the document is in edit mode
Compute visibility
This would be the Server Side JavaScript for the computed value of the visibility of the button:
/*check if the current user is the author*/
var user = session.getEffectiveUserName();
var userAbbr = @Name("[ABBREVIATE]",user);
var doc = dominoDoc.getDocument();
var from = doc.getItemValueString("From");
var fromAbbr = @Name("[ABBREVIATE]",from);
var isAuthor = (userAbbr == fromAbbr);
/*get the status*/
var status = doc.getItemValueInteger("Status");
/*final code*/
(dominoDoc.isEditable() && (status=="1") && isAuthor)|| dominoDoc.isNewNote()
You can explicitly specify the type of JavaScript objects:
var doc:NotesDocument = dominoDoc.getDocument().
This will enable type ahead when you use this object variable in the consecutive lines:
Use scoped variables
There might be other places in the form where we need to know whether the current user is the author of the document. Rather than computing this on each occasion, we could use a session variable and give it a value when the XPage is loaded:
The afterPageLoad event of ccFormDocument now looks like this:
if(!dominoDoc.isNewNote()){
sessionScope.reviewerName = getComponent("DataReviewers").getValue();
}else{
sessionScope.reviewerName = "";
};
/*check if the current user is the author*/
var user = session.getEffectiveUserName();
var userAbbr = @Name("[ABBREVIATE]",user);
var doc = dominoDoc.getDocument();
var from = doc.getItemValueString("From");
var fromAbbr = @Name("[ABBREVIATE]",from);
sessionScope.isAuthor = (userAbbr == fromAbbr);
And the adapted script to calculate the visibility of the Submit for Review button becomes:
/*get the status*/
var doc = dominoDoc.getDocument();
var status = doc.getItemValueInteger("Status");
/*final code*/
(dominoDoc.isEditable() && (status=="1") && sessionScope.isAuthor)|| dominoDoc.isNewNote()
The variable sessionScope.isAuthor can now be used anywhere else in formDocument.xsp.

Create a Server Side JavaScript Library for global scoped variables
In case we want to reuse the sessionScope variable in different custom controls, it is helpful to create a Server Side JavaScript library to maintain their definitions:
Create a new Server Side JavaScript Library and name it "
xpServerSide".
In the script library, create a function that assigns a value to sessionScope.isAuthor.
Create a second function that assigns a value to a variable sessionScope.nabNames. This variable should return a list of names from the address book, which then can be used in the type ahead and validation of the AddReviewer field.
Finally, create a function that calls the two above functions. This function will then be called from the afterPageLoad event of ccFormDocument..
The script library code could look like this:
function checkAuthor(){
check if the currentuser is the author of the document
write the result in the sessionScope.isAuthor variable
var user = session.getEffectiveUserName();
var userAbbr = @Name("[ABBREVIATE]",user);
var doc:NotesDocument = dominoDoc.getDocument();
var from = doc.getItemValueString("From");
var fromAbbr = @Name("[ABBREVIATE]",from);
sessionScope.isAuthor = (userAbbr == fromAbbr);
}
function loadNabList(){
var dbname = new Array(@Subset(@DbName(), 1),"names.nsf");
sessionScope.nabNames = @DbColumn(dbname,"($VIMPeople)",1);
}
/*Main function*/
function initCcForm(){
checkAuthor();
loadNabList();
}
In order to use these functions in our custom control, we need to add the JavaScript Library as resource to the custom control, and we need to call the function initCcForm after page load:
In ccFormDocument, add the Script Library xpServerSide as a resource, in the Properties view of the custom control:
In the afterPageLoad event of the custom control, replace the previously added sessionScope.isAuthor definition by a call to initCcForm(). The afterPageLoad event now looks like this:
if(!dominoDoc.isNewNote()){
sessionScope.reviewerName = getComponent("DataReviewers").getValue();
}else{
sessionScope.reviewerName = "";
};
initCcForm();
Now, the definition of the variable nabNames can also be replaced by sessionScope.nabNames in the Type Ahead of the field AddReviewer and in the onclick event of the Add button.
Use themes to display debug info
During application development it can be helpful to display certain variables in the XPage for debugging purposes.
For example, we would like to display the value of sessionScope.isAuthor to see if the current user is the author of the document. This can be done easily by creating a computed field and giving it the value sessionScope.isAuthor.
Now it would be usefull if we could easily switch between debug mode (displaying debug info) and normal mode. This can be achieved easily with themes.
We will create a panel containing all debug variables and make this panel hidden in our default theme. Then we'll create an additional theme that is a variation of the normal theme and that makes the debug info visible.
Create a container for debug info
In ccFormDocument, drag a panel container control on the bottom of the custom control.
In the panel, drag a computed field and a label to display the value of isAuthor:
Add additional computed fields for other values if desired
In the All Properties tab of the panel, assign a themeId "debugInfo". This will give a handle to the panel in the themes:
Go to the Source View of ccFormDocument
Change the tag of the newly created <xp:panel>: replace the tag by <xp:div>
Why?
We don't need the functionality of an xp:panel (like event handlers), as we just need a container to access with a theme. Therefore, we can reduce overhead and use xp:div tags instead.
Simple <div> tags on the other hand would not work, because they are not accessible by themes.
The panel source code now looks like this:
<xp:div themeId="debugInfo">
<xp:label value="isAuthor: " id="label8"></xp:label>
<xp:text escape="true" id="debugIsAuthor"
value="#{javascript:sessionScope.isAuthor;}">
</xp:text>
</xp:div>
Define styles for debugInfo
Create a new style sheet and call it "custom.css".
In the stylesheet create 2 new classes: one for the debuginfo in "debug mode", and one in "normal mode":
.debugInfoVisible {
background-color: #EEFEE1;
border-color: #CCEBB5;
border: 1px solid;
padding: 10px;
}
.debugInfoHidden{
display:none;
}
Save and close the stylesheet.

Adjust the normal theme
In order to make the debug info invisible when the application is used in "normal mode", we will adjust the normal theme, and include the style info for "normal mode":
Open the theme oneuiv2-default
Add a <resource> section to include custom.css in the theme:
<resource>
<content-type>text/css</content-type>
<href>custom.css</href>
</resource>
Add a <control> section and assign the class "debugInfoHidden" to the themeID "debugInfo":
<control>
<name>debugInfo</name>
<property>
<name>styleClass</name>
<value>debugInfoHidden</value>
</property>
</control>
Create a theme for debug mode
Create a new theme, "oneuiv2-default-debug"
Inherit from "oneuiv2-default"
Add a <control> section and assign the class "debugInfoVisible" to the themeID "debugInfo":
<theme extends="oneuiv2-default">
<control>
<name>debugInfo</name>
<property>
<name>styleClass</name>
<value>debugInfoVisible</value>
</property>
</control>
</theme>
With the application theme set to "oneuiv2-default", the debug info will not be displayed on formDocument.xsp.
Set the application's theme in Application Properties - XPages to "oneuiv2-default-debug"
Result: the debuginfo panel is now displayed on the XPage.
Now, you can add a panel with the same themeID in other XPages and custom controls, to display debug info if the debug theme is enabled.
Workflow processing with Server Side JavaScript
Alternatively to writing the back-end workflow processing in LotusScript the workflow can also be written in ServerSide Javascript.
The approach in both cases is very identical.
In this case study, it is not very meaningful, as we already have everything written in LotusScript. But can be useful for new Web applications. The following steps are optional in this tutorial: you might want to keep the existing LotusScript workflow, or replace it by the JavaScript workflow.
The approach to implement the JavaScript workflow would be the following:
Create a serverside javascript script library.
Create a main function in the sript library that will handle the postSave processing.
In the XPage, include this Script library as a resource.
In the postSaveDocument event of the data source, you can now directly call the main function for processing the document.
The following table compares the LotusScript workflow and the Server Side JavaScript workflow side by side.
| LotusScript agent | Serverside javascript script library |
Starting point = a LotusScript agent (that eventually makes use of a LS script library)
 | Starting point = a SS JS script library, that contains the main function (cfr initialize in the LS agent)
 |
|
|
The agent is called from the XPage's Data Source postSaveDocument event:
 | The SS JS script library is included as resource in the XPage.
Then, its main function is called from the XPage's Data Source postSaveDocument event:


|
The code in LS and SS JS looks very similar as the same back-end classes are being used
 |  |
The above table just gives an idea of how to get started with a Server Side JavaScript workflow. In this example, it doesn't add any value, but in fact, using Server Side JavaScript, would make it much easier to return values to the XPage.
Summary
In this page, we have implemented the Notes client workflow in the XPage application. Only the Submit for Review action has been provided, but this should have been sufficient to demonstrate the concepts of adding a workflow to an XPage application, based upon an existing Notes client application.
Also, the use of a Server Side JavaScript library to load global scoped variables has been explained, as well as using Server Side JavaScript as an alternative for LotusScript for workflow processing.
