Using LotusScript in Agents
Accomplishing the same actions in LotusScript requires a slightly different approach ”not different in that you have to do different things, just that you have to allow for more detail of what you're trying to accomplish. Thanks to the Formula language, many of the back-end tasks are taken care of by Domino's native commands and functions. For example, consider the Process Mail agent previously discussed. The agent " knows " that if the value of a field is changed, the document must be saved when the agent has finished processing the document.
This isn't true when using LotusScript. LotusScript does exactly what you tell it to do ”nothing more, nothing less ”which is not always what is intended! With LotusScript, after changing a field value, you have to explicitly call the doc.save True,False command to save the document or the modifications will be lost. Although this might seem cumbersome at first, it's not unreasonable. With the added power and flexibility of LotusScript's capability to reach Domino objects on a deeper level than what's available through the Formula language comes the ability to control exactly what the script does.
Now take a look at how the Process Mail agent can be written in LotusScript. Listing 11.4 shows the code for this LotusScript agent. All of this code can be entered into the Initialize event for the agent.
Listing 11.4 Code for LotusScript Agent
'Process Archive Mail-In - LotusScript Version: Sub Initialize 'Section I %REM Processing Flow: 1. Find the mail memo that was just received 2. Send a reply message to the sender 3. Change the form from Memo to NG 4. Add the cComment field 5. Add the NGReaders field 6. Delete all the mail and other fields that are not required for the NG form 7. Delete the AuthorList field 8. Send a mail message to the database manager notifying him/her of need to review 9. Save the changes we've made %END REM 'Section II 'declare the variables used for this procedure Dim s As NotesSession Dim db As NotesDatabase Dim note As NotesDocument Dim reply As NotesDocument Dim collection As NotesDocumentCollection Dim dbmgr As NotesName Dim item As NotesItem Dim rtitem as NotesRichTextItem 'Section III 'create the object reference variable for the Notes Session Set s = New NotesSession 'create the object reference for the current database Set db = s.CurrentDatabase 'Section IV 'create the object reference for the Unprocessed documents collection. 'For Agents, the UnprocessedDocument list is a document collection of all ' documents that meet the "Which documents should it act on?" criteria ' for the agent Set collection = db.UnprocessedDocuments '1. Find the mail memo which was just mailed in ' Because the trigger for this agent is "When documents are ' created or modified" ' we have to verify that the documents we get in the collection are actually ' mail memos. If they're not, then this is a modified, already existing document, ' and we don't want to do anything with it. Set note = collection.GetFirstDocument 'Section V ' This is our main processing loop. As long as there are any documents ' to process in our collection, we'll keep repeating this loop Do Until note is Nothing 'Section VI If note.Form( 0 ) = "Memo" Then ' This is a mail memo, so we'll process it ' 2. Send a message to the dbmgr as indicated in the NGReaders field of the document Set reply = New NotesDocument( db ) reply.Form = "Memo" reply.SendTo = "GMan" reply.Subject = "New Graphic Request" reply.Desc = "Please review the newly submitted graphic." Set rtitem = reply.body call rtitem.appenddoclink(doc,doc.Subject) Call reply.Send( False ) 'Section VII '3. Change the form from Memo to NG note.Form = "NG" '4. Add the cComment field Set sender = New NotesName( note.From( 0 ) ) note.cComment = note.Subject( 0 ) & ", submitted by " & sender.Abbreviated 'Section VIII '5. Add the NGReaders field Set item = note.AppendItemValue( "NGReaders" , "GMan" ) item.IsReaders = True 'Section IX '6. Delete all the mail and other fields that are not 'required for the MI form Call note.RemoveItem( "Subject" ) Call note.RemoveItem( "From" ) Call note.RemoveItem( "Categories" ) Call note.RemoveItem( "CopyTo" ) Call note.RemoveItem( "DefaultMailSaveOptions" ) Call note.RemoveItem( "DeliveredDate" ) Call note.RemoveItem( "Encrypt" ) Call note.RemoveItem( "LayoutField" ) Call note.RemoveItem( "Logo" ) Call note.RemoveItem( "MailSaveOptions" ) Call note.RemoveItem( "PostedDate" ) Call note.RemoveItem( "Principal" ) Call note.RemoveItem( "RouteServers" ) Call note.RemoveItem( "RouteTimes" ) Call note.RemoveItem( "Secure-mail" ) Call note.RemoveItem( "SenderTag" ) Call note.RemoveItem( "SendTo" ) Call note.RemoveItem( "Sign" ) '7. Delete the AuthorList field Call note.RemoveItem( "AuthorList" ) '8. Save the changes we've made note.Save True, True End If 'Section X Set note = collection.GetNextDocument( note ) Loop 'Section XI 'Set these documents marked as processed by this agent Call collection.UpdateAll End Sub
Now let's step through the code and examine what each section actually does.
Code Listing 11.4, Section I
Notice that the first thing that is done is to put in a comment block that describes what this agent is supposed to do and the general flow of processing. This will help those who come after you or have to re-engineer your code at a later point in time to make modifications to this agent (it could even be you!). As an active developer, you'll crank out a lot of code in a year, and it's hard to remember all the formulas you've written, what they were supposed to do, and why you wrote them the way you did without comment.
Code Listing 11.4, Section II
In Section II, all the object reference variables for the objects that will be used in the script are declared. Note that in the agent's current state, none of the object references has a value; they all equate to NOTHING or NULL , which are internal LotusScript constants.
Code Listing 11.4, Section III
Here, the s and db object references are set to their respective objects. s becomes the pointer to everything Notes knows about the current user session ”the known universe, as it were. db becomes the pointer to the current database. Once declaration has been made for the targeted database and its object, db , has been set, the agent can access all of the database object classes within the NotesDatabase class, such as NotesACL , NotesAgent , NotesDocument , NotesDocumentCollection , NotesForm , NotesNoteCollection , NotesOutline , NotesReplication , and NotesView .
Code Listing 11.4, Section IV
The object reference collection is set to what is actually a property of the database, the UnprocessedDocuments property. An explanation of a couple of terms is in order here. A collection is a view of documents. The view doesn't necessarily have to be a user view; it might be an internally generated and manipulated view. The UnprocessedDocuments property is a collection of all the documents in the database that are considered unprocessed for the current view action or agent ”an agent, in this case. The UnprocessedDocuments returned for a particular agent could change depending on what the agent does.
The last line of the section sets the note object reference equal to the first document in the document collection. Note here that the code doesn't test to see whether there are actually any documents in the document collection. Why not? The answer lies in the trigger for the agent. This agent is set to run if documents have been created or modified. Therefore, if there were no unprocessed documents, the agent would not have been invoked. So now the note object points to the first unprocessed document.
Code Listing 11.4, Section V
Although it's not very exciting, this Do...While loop is the heart of the script. As long as the note object points to a document ”in other words, if it's not NOTHING ”the loop will continue.
Code Listing 11.4, Section VI
Remember that the trigger for this agent is If Documents Have Been Added or Modified. Remember, too, that the purpose of this agent is to convert mailed-in documents to NewGraphicsNG documents. However, because someone could conceivably modify a document that already exists in the database, you have to test for that condition; you do not want to process anything except new mail memos here.
The IF statement begins a block of code that will be executed only if you decide to process this document. To test whether you want to process the current document, you check the contents of the Form field. If the Form field is set to Memo, you know that it just got here and that you should process it. So, process it you do. The first thing you want to do is send a message to the database manager that there is a new graphic and that it needs to be processed.
To send a mail message, you first have to create a new Notes document, which you do with the Set reply = statement. This creates a new, blank Notes document with no fields; it's literally a blank piece of paper. You have to tell Domino everything about this new document. Set the Form field, the SendTo field, the Subject field, and the Body field. Look at how the SendTo field was set. Right now, the reply object reference points to the brand-new reply document, and the note object reference points to the group name for the database managers. The following line says to take the contents of the group GMan and make the SendTo field on the reply object the same value:
reply.SendTo = "GMan"
The last line of Section VI invokes the Send method of the reply object. Back in Section II of the agent, reply was declared as an instance of the NotesDocument class; in Section IV, it was set to point to an actual object. The NotesDocument class has a method called Send that mails the NotesDocument object to the recipients named in the SendTo field (set previously). The False parameter to the Send method tells Notes to not send the form with the document.
Code Listing 11.4, Section VII
Now you've finished sending the reply message, so you can concentrate on processing the current message, still pointed to by note . First, you change the Form field from Memo to NG. Next , you fill in the cComment field with some text that includes the name of the sender of this memo. However, because Domino stores names fully canonicalized (such as CN=Nicol Penny/O=VALUEINNOVATIONS ), which isn't very user-friendly, you need to manipulate the sender's name to get it into the abbreviated format (for example, Nicol Penny/VALUEINNOVATIONS ).
To do this, you first instantiate an object reference of the NotesName class. The NotesName class allows you to manipulate Notes names with a great deal of detail. Note the argument passed to the NotesName declaration note.From( 0 ) . Knowing now that this is dot notation, you've already deduced that you're using the From field of the document pointed to by note . But what's with the ( 0 ) ?
In LotusScript, every field is actually stored as an array, even if there's only one element in the field. Therefore, to get to the field contents, you have to reference the field as though it were an array (which it is). So, to get the one and only element from the From field on the note document, you need to reference note.From(0) .
TIP
I can guarantee that when you start writing LotusScript code on your own, you'll forget to use an index when trying to reference a field's contents. But don't worry! The compiler gives you notice in the form of a Type Mismatch error. So when you've looked at your declaration statements and the data types all match, and you're still getting that Type Mismatch error, look for the missing (0) index.
Code Listing 11.4, Section VIII
The two lines of Section VIII create the NGReaders field and set it to be a readers field. Had you not needed to set a field property ”in this case, the IsReaders property ”you could have created the NGReaders field the same way you created the fields on the reply document or the cComment field created in Section VII. To manipulate the properties of a field, you must declare and use an object reference to a NotesItem object. Then you can use the dot notation to set the IsReaders property of the item object.
Code Listing 11.4, Section IX
In Section IX, you invoke the NotesDocument method RemoveItem , which deletes the named field from the document. Finally, the Save method is called, which saves the document and brings you to the end of the IF block.
Code Listing 11.4, Section X
At this point, all processing for the current document, the one pointed to by the note object reference variable, is complete. Now you need to process any other remaining documents in the list of unprocessed documents contained in the collection object. So, set the note variable to the next document in the collection with the GetNextDocument method. What if there were no other documents in the collection? In that case, the note variable would be equal to NOTHING .
The LOOP statement sends you back up to the top of the Do...While loop, where the condition Not ("note is nothing") is checked again. If there is another document to process, the entire loop is repeated. If at this point the note object is equal to NOTHING , the loop condition is no longer true and the next statement after the LOOP line is executed.
Code Listing 11.4, Section XI
This brings you to the last line of executable code in the agent. Remember that the list of documents the agent was to take care of came from the UnprocessedDocuments property of the database. If the agent exited after processing documents, those same documents would be listed in the UnprocessedDocuments collection the next time the agent was invoked. You might ask, why is this so? Each agent tracks which documents it has acted on previously. This should be obvious ”how else would an agent know whether a particular document was modified since the last time the agent ran? With a simple agent or a formula agent, the process is automatically taken care of for you. However, with a LotusScript agent, you must do it yourself. So, conveniently, there's a NotesDocumentCollection method, UpdateAll , that flags all the documents in a collection as having been processed by this agent. The next time the agent is invoked, these documents won't be processed again unless they've been changed by some other process.
That completes the discussion of the LotusScript agent. It took a few more lines of code to do the same thing that the formula agent did. With this particular agent, the compelling reason to write it in Script is the Reader Names field. Only in LotusScript or Java could the field property IsReaders be set. Using the traditional approach discussed earlier, the Refresh agent had to be run manually to permanently hide the new documents from any ID other than the GMan group. Although it could be argued that this is taken care of via the ACL because the users indicated in the GMan group are the only user IDs that can access the database, it is still a nice touch and involves one fewer step for the administrator.
Most of us would probably have chosen to create this particular agent in the Formula language, primarily because we could ”and in some respects, it's easier. Sometimes you'll have no choice but to write LotusScript agents simply because you'll need the additional control and object manipulation capability that LotusScript provides you.