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.
%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
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.”