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.”
Pingback: Getting email addresses from the Notes address book | Lost in XPages, Soon to be Found
Pingback: Creating a meeting notice in Outlook from the Notes client | Lost in XPages, Soon to be Found
Thiis is a great post thanks