Posts Tagged With: Agent

An agent to change field values to help in #XPages

With our transition to XPages, I’ve been finding more and more often that I don’t have a form interface in the Notes client to just change one field value on the back end. Too often, on the front end, in XPages, there’s a value I simply haven’t exposed for editing or don’t even display. I often don’t add the fields to the back end Notes form because it really doesn’t add much value. So, when I want to change one field, or change one field on multiple documents, I do it with an agent. Heck, over the years, we all have. We just usually did it in a very static manner – writing a quick @formula to change the value of a specific field. That requires changing the design to create the agent and then delete the agent – or leaving a mess behind that slowly grows your agent list with more and more single-use agents.

So, I wrote an agent that allowed me to replace a text field by naming the field and the value. Then, I found I wanted one for numbers as well. So, for a few weeks, I had two agents. Then, I realized the folly and wrote an agent that detects the field being updated (or asking you if the field doesn’t exist). I only did it for text, numbers and dates, so I imagine it could be extended. It’s enough for me. Since I had occasion to share it this week with another developer, I thought I’d also post it here to share via the blogosphere.

While there’s nothing brilliant about it, it sure is useful.

%REM
	Agent Change Field Value
	Created Jun 19, 2015 by David Navarre/DAI
	Description: This Agent allows the user to name a field and change the value
		It checks the field type on the first document selected and
		handles strings, numbers and dates differently
%END REM
Option Public
Option Declare
Use "Utilities"
Dim ws As NotesUIWorkspace
Dim newDate As NotesDateTime
Dim newvalue As Variant
Dim fieldname As Variant
Dim change As String
Sub Initialize
	Dim session As New NotesSession
	' thisdb declared in Utilities '
	Dim ndc As NotesDocumentCollection
	Dim itemdoc As NotesDocument
	Dim itemToChange As NotesItem
	Dim numericValue As Double
	Dim itemType As Long
	Dim selectedType (2) As String
	Dim choice As Variant
	Dim reason As String

	On Error GoTo errorhandler

	Set ws = New NotesUIWorkspace
	Set thisdb = session.CurrentDatabase

	selectedType (0) = "Date"
	selectedType (1) = "Text"
	selectedType (2) = "Numbers"

	Call StartAgentLogging (session )

	fieldname = ws.Prompt ( PROMPT_OKCANCELEDIT, "Field Name", "Enter the name of the field to change" )
	If IsEmpty ( fieldname ) Then
		Exit Sub
	End If

	newvalue = ws.Prompt ( PROMPT_OKCANCELEDIT, fieldname, "Enter the new value for " & fieldname )
	If IsEmpty ( newvalue ) Then
		Exit Sub
	End If

	' get the collection before issuing the confirmation, so we can determine field type '
	' from the first document selected, assuming it is the same on the rest '
	Set ndc = thisdb.UnprocessedDocuments
	Set itemdoc = ndc.GetFirstDocument
	Call agentLog.LogAction ( "Items: " & ndc.Count )

	If ( itemdoc.Hasitem(fieldname) ) Then
		Set itemToChange = itemdoc.Getfirstitem(fieldname)
		itemType = itemToChange.Type
	Else
		choice = ws.Prompt(PROMPT_OKCANCELLIST, "Select field type", "Field " & fieldname & " does not exist on the first document. Select field type to create", "Text", selectedType )
		If IsEmpty ( choice ) Then
			MessageBox "Action cancelled"
			Exit Sub
		End If
		Select Case choice
		Case "Date"
			itemType = 1024
		Case "Text"
			itemType = 1280
		Case "Number"
			itemType = 768
		End Select
	End If	

	If Confirm ( itemType ) Then
		While Not itemdoc Is Nothing
			Select Case itemType
			Case 1024  ' DATETIMES '
				Call itemdoc.ReplaceItemValue ( fieldname, newDate )
			Case 1280  ' TEXT '
				Call itemdoc.ReplaceItemValue ( fieldname, newValue )
			Case 768  ' NUMBERS '
				' if the value supplied is an integer, save it that way '
				If ( CInt ( CDbl ( newValue ) ) = CInt ( newValue ) ) Then
					Call itemdoc.ReplaceItemValue ( fieldname, CInt ( newValue ) )
				else
					Call itemdoc.ReplaceItemValue ( fieldname, CDbl ( newValue ) )
				End If
			End Select

			Call agentLog.LogAction ( change )
			Call itemdoc.Save ( True, False )

			Set itemdoc = ndc.GetNextDocument ( itemdoc )
		Wend
		MessageBox change & Chr$(10) & "Successful on " & ndc.Count & " documents"
	End If

exiting:
	Call agentLog.LogAction ( "-------" )
	Call agentLog.LogAction ( "-------" )
	Exit Sub
errorhandler:' report all errors in a messagebox '
	reason = "Error #" & CStr (Err) & " (" & Error & ") on line " & CStr (Erl)
	MessageBox reason, 16, "Error"
	Call agentLog.LogAction ( reason )
	Resume exiting

End Sub
%REM
	Function ConfirmValue
	Description: This function displays a confirmation dialog based on the field type
%END REM
Function Confirm ( itemType As Long ) As Boolean

	Select Case itemType
		Case 1024  ' DATETIMES '
			Set newDate = New NotesDateTime ( newValue )
			change = "Change date in " & fieldname & " to " & newDate.Dateonly
		Case 1280  ' TEXT '
			change = "Change text in " & fieldname & " to " & newValue
		Case 768  ' NUMBERS '
			change = "Change number in " & fieldname & " to " & newValue
		Case 1  ' RICHTEXT '
			Confirm = False
			MessageBox "Cannot change rich text using this agent"
			Exit Function
		Case Else
			Confirm = False
			MessageBox "Cannot change " & fieldname & " using this agent" & Chr$(10) & "Field type: " & itemType
			Exit Function
	End Select 

	Confirm = ws.Prompt ( PROMPT_YESNO, "Confirmation", change & "?" )

End Function

Oh, and the relevant snippet of the Utilities script library….

%REM
    Library Utilities
    Created Mar 29, 2012 by David Navarre/DAI
    Description: Some database utilities
%END REM
Option Public
Option Declare

Dim thisdb As NotesDatabase
Dim agentLog As NotesLog
Sub Initialize

End Sub

Sub StartAgentLogging ( session As NotesSession )
    ' this module starts agent logging '
    ' 29 Mar 12, David Navarre '
    Dim title As String
    Dim agent As NotesAgent

    Set agentLog = session.CreateLog ("Agent log")
    Set agent = session.Currentagent
    Call agentLog.OpenAgentLog
    Call agentLog.LogAction ( "Log Open" )

End Sub
Advertisement
Categories: Old Notes, Utilities | Tags: , , , , , | 2 Comments

Creating a meeting in the UI in #IBMNotes

When we tout the advantages of using Notes, one of the key points we always mention is the tight integration between Notes applications and Notes mail. Part of this ought to be tight integration with calendaring and scheduling. Oddly, I’ve never written an application that has any interface with the user’s calendar. This had to change. Our users wanted to be able to create a meeting notice from the Quarterly Project Review (QPR) document for that review, taking up the dates and participants from that document seamlessly into the notice. I scratched my head because I’d never even tried it, though I was sure it had to be easy.

So, I looked at what form was used by the meetings I attend. I must have clicked on a proposed meeting because I chose ‘Notice’. I just added a button on the QPR form to create and send Notice documents to the chair and each of the attendees. That looked like it worked because it would show up in people’s inboxes and they could click to accept or decline. Of course, it would disappear once that happened. Oops.

Not only that, but since many of the legacy Notes apps here use formula language to create messages in the UI for users to complete when requesting approvals, my users told me they really wanted to be able to edit the notice. I groused because I find that process inefficient. Users can choose to never send the email or change it in ways that are unexpected. I like approval requests to go silently or to allow the user to enter some additional text, but not to give them full control.

Fortunately, I figured out that I ought to using the Appointment form.

Determining the solution

As I learned more about their requirements, I realized that with all the things they wanted to be able to change, I should give in and simply open in the UI. The fact that any form in the mail template is endlessly complicated was a big incentive as well.

So, I searched the internet to see if anyone else had done this and only came up with a formula language method, which mostly worked, but not quite. Then, taking the formula language code as my example, I built a LotusScript agent that does a nice job of it.

I thought that I could first create the document as a NotesDocument object and then open it using the editDocument method of the NotesUIWorkspace object. I’m not sure if it was because I didn’t set the right fields or not enough fields, but it simply didn’t work when I tried it that way. So, I went the full monty and simply opened it as a NotesUIDocument right from the start.

Interestingly, when all I did was insert names into the required (EnterSendTo) and optional (EnterCopyTo) fields, we realized that you didn’t get to see their schedules to find the right time for the meeting. One of the testers found that clicking on the highlighted ‘Required’ would make them appear. So, checking that link reveals some curious formula language coding:

FIELD EnterSendTo:= @Trim(EnterSendTo);
FIELD EnterCopyTo:= @Trim(EnterCopyTo);
FIELD EnterBlindCopyTo:= @Trim(EnterBlindCopyTo);
@Command([MailAddress];"EnterSendTo";"EnterCopyTo";"EnterBlindCopyTo");
@Command([EditGotoField]; "EnterSendTo");
@Command([EditInsertText]; " ");
@Command([EditGotoField]; "EnterCopyTo");
@Command([EditInsertText]; " ");
@If(EnterBlindCopyTo!="" & @GetProfileField("CalendarProfile"; "showCalBCC") = "1";@Command([EditGotoField]; "EnterBlindCopyTo");"");
@If(EnterBlindCopyTo!="" & @GetProfileField("CalendarProfile"; "showCalBCC") = "1";@Command([EditInsertText]; " ");"");
@PostedCommand([ViewRefreshFields])

So, it’s quirky. Using @Trim, I can understand, but why would it insert the blank space into the two fields? Then I noticed an event on each field.

Sub Onchange(Source As Field)
	Call csEventObj.onChange(FIELD_INVITEES_CHANGED, ITEM_REQUIRED )
	Call csEventObj.UpdateScheduler( ITEM_REQUIRED, ROLE_REQUIRED, APPFLAG_NEW )
	
	If Not (cseventobj.m_note.IsNewNote) Then
		cseventobj.NeedsOLPTran = True
	End If
End Sub

So, the link runs formula language that kicks off the onChange event, which does additional processing. So, when coding one’s agent to create the appointment in the UI, just repeat what the formula language does, only in script. Thus, lines 90-94 in my agent make sense.

The final quirk is with my original QPR document. If the user was in read mode, the agent ran beautifully, but if they were in edit mode, I needed to make sure I had the values on the back end AND that the code didn’t ‘get confused’ with which uidoc was which. Perhaps it was something convoluted with my code, but I found it best if I put the QPR document back into read mode. Then, to avoid issues with how I’m recording the ‘history’ (noting on the QPR that someone created a meeting notice), I decided to close and reopen it in read only mode. Allowing it to be edited was creating confusion, since it threw odd prompts and might generate rep-save conflicts. As such, I’m doing an odd dance with values and objects on lines 50-55.

The agent

%REM
	Agent Send Calendar Invites
	Created Dec 23, 2014 by David Navarre/DAI
	Description: This Agent creates a calendar invite, listing participants and optional participants
%END REM
Option Public
Option Declare
Use "Utilities"
Sub Initialize
	Dim session As New NotesSession
	Dim ws As New NotesUIWorkspace
	Dim thisdb As NotesDatabase
	Dim maildb As New NotesDatabase ( "", "" )
	Dim uidoc As NotesUIDocument
	Dim memoUIdoc As NotesUIDocument
	Dim qprDoc As NotesDocument
	Dim history As NotesRichTextItem
	Dim recipientName As NotesName
	Dim qprDate As Variant
	Dim qprTime As Variant
	Dim projectName As Variant
	Dim fiscalYearAndQuarter As Variant
	Dim participants As Variant
	Dim participantsOptional As Variant
	Dim timeString, dateString As Variant
	Dim answer As Variant
	Dim reason As String
	Dim unid As String

	On Error GoTo errorhandler
	
	Call StartAgentLogging ( session )

	Set thisdb = session.Currentdatabase

	Set uidoc = ws.Currentdocument

	reason = "This will create a meeting invite for you to send to participants."
	If uidoc.Editmode Then
		reason = reason + Chr$(10) + "The QPR will switch to read-only mode."
		reason = reason + Chr$(10) + "If you close and re-open it, you can edit it again."
	End If
	reason = reason + Chr$(10) + "Continue?"
	answer = ws.Prompt ( PROMPT_YESNO, "Continue?", reason )
	If answer = 0 Then
		Exit Sub
	End If	
		
	If uidoc.Editmode Then
		Call uidoc.Save()
		uidoc.Editmode = False
		Set qprDoc = uidoc.Document
		unid = qprDoc.Universalid
		Call uidoc.Close(True)
		Set qprDoc = thisdb.Getdocumentbyunid(unid)
		Set uidoc = ws.Editdocument(False, qprDoc, True)
	Else
		Set qprDoc = uidoc.Document
	End If
	
	Set qprDate = qprDoc.Getfirstitem("QPRDate")
	Set qprTime = qprDoc.Getfirstitem("QPRTime")
	timeString = qprTime.Text
	dateString = qprDate.Text
	Dim qprStartTime As New NotesDateTime ( timeString )

	Call maildb.Openmail()
	Set memoUIdoc = ws.Composedocument(maildb.Server, maildb.Filepath, "Appointment")
	projectName = qprDoc.Getitemvalue("ProjectName") 
	fiscalYearAndQuarter = qprDoc.Getitemvalue("FiscalYearAndQuarter") 
	Call memoUIdoc.Fieldsettext("Subject", fiscalYearAndQuarter(0) & " QPR: " & projectName (0) )

	Call memoUIdoc.Fieldsettext("STARTDATE", dateString )
	Call memoUIdoc.Fieldsettext("STARTTIME", timeString )
	Call memoUIdoc.Fieldsettext("ENDDATE", dateString )
	Call qprStartTime.Adjusthour(1, False)
	Call memoUIdoc.Fieldsettext("ENDTIME", qprStartTime.Timeonly)

	participants = qprDoc.Getitemvalue ( "Participants" )
	ForAll entry In participants
		Set recipientName = New NotesName ( entry )
		Call memoUIdoc.Fieldappendtext("EnterSendTo", recipientName.Abbreviated & Chr$(10) ) 
	End ForAll
	participantsOptional = qprDoc.Getitemvalue ( "ParticipantsOptional" )
	ForAll entry In participantsOptional
		Set recipientName = New NotesName ( entry )
		Call memoUIdoc.Fieldappendtext("EnterCopyTo", recipientName.Abbreviated & Chr$(10) ) 
	End ForAll

	Call memoUIdoc.Gotofield("EnterSendTo")
	Call memoUIdoc.Inserttext(" ")
	Call memoUIdoc.Gotofield("EnterCopyTo")
	Call memoUIdoc.Inserttext(" ")
	Call memoUIdoc.Gotonextfield()
		
	Set history = qprDoc.Getfirstitem("History")
	Call history.Appendtext(Now & " - Meeting notice created by " & session.Commonusername)
	Call history.Addnewline(1, True)
	Call qprDoc.Replaceitemvalue("NoticeFlag", 1)
	Call qprDoc.Save(True, False)
	
exiting:
	Exit Sub
errorhandler:' report all errors in a messagebox
	reason = "Error #" & CStr (Err) & " (" & Error & ") on line " & CStr (Erl)
	MessageBox reason, 16, "Error"
	Call agentLog.LogAction ( reason )
	Resume exiting ' transfers control to the exiting label
End Sub

Final thoughts

I’m sure I can do this a bit more efficiently, but I’m pretty happy with this first foray into calendaring & scheduling. We’ll probably refine this a little and do more of it in our projects. Users always want to be able to skip re-typing everything and there’s no reason not to handle it for them. Of course, we’re likely to have to revise all of this once we move to Verse, but, as my father always said, “I’ll burn that bridge when I come to it.”

Categories: Old Notes | Tags: , , , | 2 Comments

Blog at WordPress.com.

%d bloggers like this: