Possible bug in LotusScript event binding - part 1
Category lotusscript
Crack open a tasty beverage and commandeer a comfortable chair... this is going to be a long one.
In preparation for another Yellowcast episode, I've been playing with remote event binding in LotusScript. It's something I find myself using more and more frequently, but I decided I wanted to build a mini-framework to standardize how I approach it, as well as giving me something to provide to my loyal readers for instructional purposes if not ongoing usage. In the process, I've stumbled upon what appears to be a bug in how LotusScript handles overridden methods in derived classes when event binding is involved.
Perhaps I should back up a bit first. What is remote event binding? Simply put, it allows you to designate custom methods to be called when a LotusScript UI object's event is triggered. You're already familiar with this, though you might not realize it: in Domino Designer Help, check out the documentation for the NotesUIDocument class. Scroll past the Properties and Methods, and you'll see a list of familiar events. To briefly belabor the obvious, the reason they're so familiar is because they map directly to (a subset of) the events on the Objects tab when you're editing a form element in Designer. Every time you've added code to the Querysave Sub in a form, you've locally bound that event. When a user tries to save a document based on that form, the Querysave event of an anonymous NotesUIDocument instance is triggered, so the code you entered is executed. But this is actually just a convenient shortcut for binding to that event. There are a couple other ways you can achieve the same result using the oft-overlooked On Event statement.
The easiest is to define a new top-level function within the same element. For example, if you create a new Sub (say, myQuerySave) from within the global declarations of the form, you can bind that Sub to the Querysave event:
In this case we're taking advantage of a local event binding (namely, the Postopen event) to get an automatic handle on the aforementioned anonymous NotesUIDocument and bind its Querysave event to our custom Sub. What does that gain you? Well... absolutely nothing, to be honest. You could have just entered the contents of the new Sub into the existing Querysave sub, and the results would have been the same. But this gives us an obvious illustration of how event binding actually works: when an event is triggered, every bound routine is called, and passed a predetermined set of parameters... which means each bound routine must have the exact same method signature (nitpick: technically the variable names can be changed but the sequence of data types must directly correspond). This is crucial to remember for later, so tuck that away for now while we delve into a slightly more advanced approach.
Imagine you've defined a custom class that may or may not be instantiated while a document is open. Perhaps it's a class defining the document's workflow, determining its current state, who needs to approve it based on who submitted it and a specified dollar amount, etc. Because you've moved beyond elementary development (pardon the assumption... if you've bothered to read this far, it's likely a safe one), you no longer use the standard input validation events, preferring instead to perform single-message validation in the Querysave or Querysend. After all, what user wants to get smacked in the face with a separate Msgbox for every nonconforming field each time something triggers a document refresh? Not to mention that typical input validation formulas completely preclude a notion of "Save As Draft", requiring the user to completely (and correctly) populate a document in order to save any of it. I once inherited a purchase requisition application that was losing the company money simply because its interface was obnoxious. Seriously. Users would try to create a requisition, and before the form had even loaded, "Smack! You forgot to enter a dollar amount!"... then when exiting any field (that's right, "automatically refresh fields" was enabled):
"Smack! You forgot to enter a vendor name!"
"Smack! You forgot to enter a shipping address!"
(These weren't the exact error messages, of course, just a rough approximation of user perception.)
Apparently (and unsurprisingly) people would get so annoyed that they'd give up, intending to try again later, but by the time they mustered up the courage to face the application again, the promotional discount period for the item they wanted to purchase had already expired, so we'd have to pay 10 or 15% more than they'd originally planned. Have I ever mentioned how much I hate standard input validations? Epic hate.
Uh... where was I? Oh yeah, event binding. So you've got a class that is only instantiated if the user is trying to actually submit the document for approval, and you want to trigger the field validation right before the workflow status is changed, because up until that point, who cares if they haven't filled out the required fields? So you put the validation code in a separate method of that class (because each method should only do one thing and do it well)... from anywhere within that class (including the constructor), you can bind the class method to the event.
Why is this any better? A couple reasons. First, the code is never executed unless the class is instantiated, so you don't have to do any checking within the event handler itself to make sure it even needs to do anything. Second, and more importantly, the event handler has access to everything inside the class. You don't have to maintain a bunch of global variables or pass a bunch of local variables to the validation routine in order for it to know how to do its thing. It lives inside an object that's (hopefully) already storing what that routine needs to know and/or can hand off control to other methods of the same object, which in turn share the same capacity. Finally, if you had an entirely different type of class - one that you've instantiated numerous instances of... which you presumably wouldn't, in this example - each can bind a method to the same event, allowing you to define the functionality in a single location but take advantage of in each instance, potentially in different ways depending on what is unique to each instance.
One more implication to consider before we move on: because all bound event handlers are called with the same parameters, each by reference (as opposed to by value), any changes to a parameter passed to each handler is visible to any other handlers. For example, imagine we want to add another handler to the above example:
Not only are any changes made to the NotesUIDocument within validateDocument still intact when updateHistory is called, any events that pass a Continue parameter check its value between handlers and suspend all outstanding handlers if it is now False. In other words, in the new example, if validateDocument toggles Continue to False, updateHistory is never called, nor is the intrinsic handler (writing the document to disk). To expand this just a tiny bit more:
Just as you don't have to verify within updateHistory that validation was successful - because that routine wouldn't have been triggered if validation failed - you don't have to verify that the history was correctly updated before sending the approval request, because if it hadn't been, the intrinsic Querysave handler is skipped, which means the Postsave event never fires (NOTE: this assumes you're toggling Continue to False in your error handler block in each event handler). Your code is simplified, because you're allowing Notes to manage your event flow for you. The only downside is that anyone who has to maintain your code later will be confused by the absence of custom flow handling if they don't understand how automatic all of this is and will probably add in their own unnecessary conditional processing.
Oops.. just like a voicemail system that will only allow you to leave messages lasting 60 seconds, Notes won't allow me to store anymore in this entry (hm... wasn't there something about that in that certfication exam I just took? What was it? Something about 32 KB...). If I've peaked your interest, slide down the rabbit hole with me in part 2.
Crack open a tasty beverage and commandeer a comfortable chair... this is going to be a long one.
In preparation for another Yellowcast episode, I've been playing with remote event binding in LotusScript. It's something I find myself using more and more frequently, but I decided I wanted to build a mini-framework to standardize how I approach it, as well as giving me something to provide to my loyal readers for instructional purposes if not ongoing usage. In the process, I've stumbled upon what appears to be a bug in how LotusScript handles overridden methods in derived classes when event binding is involved.
Perhaps I should back up a bit first. What is remote event binding? Simply put, it allows you to designate custom methods to be called when a LotusScript UI object's event is triggered. You're already familiar with this, though you might not realize it: in Domino Designer Help, check out the documentation for the NotesUIDocument class. Scroll past the Properties and Methods, and you'll see a list of familiar events. To briefly belabor the obvious, the reason they're so familiar is because they map directly to (a subset of) the events on the Objects tab when you're editing a form element in Designer. Every time you've added code to the Querysave Sub in a form, you've locally bound that event. When a user tries to save a document based on that form, the Querysave event of an anonymous NotesUIDocument instance is triggered, so the code you entered is executed. But this is actually just a convenient shortcut for binding to that event. There are a couple other ways you can achieve the same result using the oft-overlooked On Event statement.
The easiest is to define a new top-level function within the same element. For example, if you create a new Sub (say, myQuerySave) from within the global declarations of the form, you can bind that Sub to the Querysave event:
Sub Postopen(Source As Notesuidocument)
On Event Querysave From Source Call myQuerySave
End Sub
In this case we're taking advantage of a local event binding (namely, the Postopen event) to get an automatic handle on the aforementioned anonymous NotesUIDocument and bind its Querysave event to our custom Sub. What does that gain you? Well... absolutely nothing, to be honest. You could have just entered the contents of the new Sub into the existing Querysave sub, and the results would have been the same. But this gives us an obvious illustration of how event binding actually works: when an event is triggered, every bound routine is called, and passed a predetermined set of parameters... which means each bound routine must have the exact same method signature (nitpick: technically the variable names can be changed but the sequence of data types must directly correspond). This is crucial to remember for later, so tuck that away for now while we delve into a slightly more advanced approach.
Imagine you've defined a custom class that may or may not be instantiated while a document is open. Perhaps it's a class defining the document's workflow, determining its current state, who needs to approve it based on who submitted it and a specified dollar amount, etc. Because you've moved beyond elementary development (pardon the assumption... if you've bothered to read this far, it's likely a safe one), you no longer use the standard input validation events, preferring instead to perform single-message validation in the Querysave or Querysend. After all, what user wants to get smacked in the face with a separate Msgbox for every nonconforming field each time something triggers a document refresh? Not to mention that typical input validation formulas completely preclude a notion of "Save As Draft", requiring the user to completely (and correctly) populate a document in order to save any of it. I once inherited a purchase requisition application that was losing the company money simply because its interface was obnoxious. Seriously. Users would try to create a requisition, and before the form had even loaded, "Smack! You forgot to enter a dollar amount!"... then when exiting any field (that's right, "automatically refresh fields" was enabled):
"Smack! You forgot to enter a vendor name!"
"Smack! You forgot to enter a shipping address!"
(These weren't the exact error messages, of course, just a rough approximation of user perception.)
Apparently (and unsurprisingly) people would get so annoyed that they'd give up, intending to try again later, but by the time they mustered up the courage to face the application again, the promotional discount period for the item they wanted to purchase had already expired, so we'd have to pay 10 or 15% more than they'd originally planned. Have I ever mentioned how much I hate standard input validations? Epic hate.
Uh... where was I? Oh yeah, event binding. So you've got a class that is only instantiated if the user is trying to actually submit the document for approval, and you want to trigger the field validation right before the workflow status is changed, because up until that point, who cares if they haven't filled out the required fields? So you put the validation code in a separate method of that class (because each method should only do one thing and do it well)... from anywhere within that class (including the constructor), you can bind the class method to the event.
Public Sub New(Source As Notesuidocument)
On Event Querysave From Source Call validateDocument
End Sub
Why is this any better? A couple reasons. First, the code is never executed unless the class is instantiated, so you don't have to do any checking within the event handler itself to make sure it even needs to do anything. Second, and more importantly, the event handler has access to everything inside the class. You don't have to maintain a bunch of global variables or pass a bunch of local variables to the validation routine in order for it to know how to do its thing. It lives inside an object that's (hopefully) already storing what that routine needs to know and/or can hand off control to other methods of the same object, which in turn share the same capacity. Finally, if you had an entirely different type of class - one that you've instantiated numerous instances of... which you presumably wouldn't, in this example - each can bind a method to the same event, allowing you to define the functionality in a single location but take advantage of in each instance, potentially in different ways depending on what is unique to each instance.
One more implication to consider before we move on: because all bound event handlers are called with the same parameters, each by reference (as opposed to by value), any changes to a parameter passed to each handler is visible to any other handlers. For example, imagine we want to add another handler to the above example:
Public Sub New(Source As Notesuidocument)
On Event Querysave From Source Call validateDocument
On Event Querysave From Source Call updateHistory
End Sub
Not only are any changes made to the NotesUIDocument within validateDocument still intact when updateHistory is called, any events that pass a Continue parameter check its value between handlers and suspend all outstanding handlers if it is now False. In other words, in the new example, if validateDocument toggles Continue to False, updateHistory is never called, nor is the intrinsic handler (writing the document to disk). To expand this just a tiny bit more:
Public Sub New(Source As Notesuidocument)
On Event Querysave From Source Call validateDocument
On Event Querysave From Source Call updateHistory
On Event Postsave From Source Call notifyNextApprover
End Sub
Just as you don't have to verify within updateHistory that validation was successful - because that routine wouldn't have been triggered if validation failed - you don't have to verify that the history was correctly updated before sending the approval request, because if it hadn't been, the intrinsic Querysave handler is skipped, which means the Postsave event never fires (NOTE: this assumes you're toggling Continue to False in your error handler block in each event handler). Your code is simplified, because you're allowing Notes to manage your event flow for you. The only downside is that anyone who has to maintain your code later will be confused by the absence of custom flow handling if they don't understand how automatic all of this is and will probably add in their own unnecessary conditional processing.
Oops.. just like a voicemail system that will only allow you to leave messages lasting 60 seconds, Notes won't allow me to store anymore in this entry (hm... wasn't there something about that in that certfication exam I just took? What was it? Something about 32 KB...). If I've peaked your interest, slide down the rabbit hole with me in part 2.







