Possible bug in LotusScript event binding - part 2
Picking up from where we left off in part 1...
Okay, now let's take this even further. All of the above could be accomplished by defining the class directly in the form's declarations, but it's more likely that, for purposes of maintainability (per my earlier assumption), you defined it in a script library that's loaded by the form via a Use statement. Hence, the document knows about that code and can leverage all its yummy bindiness. Believe it or not, you can still bind to an object's events even if the design element that creates the object knows nothing about the code binding to its events. If you open a document via NotesUIWorkspace.editDocument, for example, you now have a NotesUIDocument handle on the document being opened, and can bind methods defined (or loaded) within the context from which the document was opened to the document's events (this memory scope overlap was a key factor in Nathan's Revolution). This could allow you, for example, to define standard audit trail logging in a centrally inherited script library and bind that behavior to every form in every application... without ever loading that script library in any of those forms. You wouldn't want to though, because then your audit trail logging only occurs if the document is opened in a way that allows the event(s) to be correctly bound (so you'd have to bind to the Queryopendocument event in every view to cancel the standard open and instead open the very document they were trying to open, but via a handle you can bind to... and, of course, doclinks bypass all of this). But flip that around: what if you want an event to be handled differently based on how the user opened the document? That's where this really comes in handy. Instead of using a form formula to load a completely different form, you can bind entirely different event handlers to the document depending on whether they opened the document via a doclink, an agent, a button, or simply double-clicked it in a view. I'll let your imagination run wild about all the possibilities this opens up.
So what's the problem? Well, as you might expect, all of this comes with some caveats in addition to those already described or alluded to. For one, it's extremely sensitive about memory scope. In fact, it works best if the bound event handler is declared globally... which is why it's best to bury this inside a class, so you can consolidate everything the handler needs to do and know inside the same object, instead of relying on a glut of global variables and top-level functions. But that's fine... we should be doing that for the bulk of our code anyway. What worries me is that I also seem to have found a flaw in class inheritance. Here's your opportunity to tell me what I'm doing wrong (okay, yet another opportunity). Consider the following, from a dummy script library:
Public Class A
Public Function attachEvent
Call Me.bindEvent()
End Function
Public Function bindEvent
'abstract function, will be overridden in a descendant
End Function
End Class
Public Class B As A
Public Function bindEvent
Call Me.doEvent
End Function
Public Function doEvent
'abstract function, will be overridden in a descendant
End Function
End Class
Another library that loads the above library contains the following:
Public Class C As B
Public Function doEvent
Msgbox "Consider it done"
End Function
End Class
Load that library into an agent, button, etc. and initialize a C instance (oops, poor naming choice... no, we're not messing with API calls here), then call attachEvent. B inherits from A, so it gets its attachEvent method, and overrides its bindEvent method. C inherits from B, so it gets the inherited attachEvent and overridden attachEvent, and overrides doEvent. As you would expect, you'll get a Msgbox. No problem. But when you introduce event binding into the mix:
Public Class EventBinder
Public Function attachEvent (Byval eventName As String, Source As Variant)
If (Source Is Nothing) Then
Set Source = Me.getContext
End If
Call Me.bindEvent (eventName, Source)
End Function
Private Function bindEvent (Byval eventName As String, Source As Variant)
'abstract function, will be overridden in a descendant
End Function
End Class
Public Class ViewEventBinder As EventBinder
Private Function bindEvent (Byval eventName As String, Source As Variant)
Select Case Lcase(eventName)
Case "inviewedit":
On Event Inviewedit From Source Call Inviewedit
Case "queryopendocument":
On Event Queryopendocument From Source Call Queryopendocument
End Select
End Function
Private Sub Inviewedit(Source As Notesuiview, Requesttype As Integer, Colprogname As Variant, Columnvalue As Variant, Continue As Variant)
'abstract function, will be overridden in a descendant
End Sub
End Class
Public Class ExampleViewBinder As ViewEventBinder
Private Sub Inviewedit(Source As Notesuiview, Requesttype As Integer, Colprogname As Variant, Columnvalue As Variant, Continue As Variant)
Dim docTarget As NotesDocument
Dim columnId As Integer
If (Requesttype = 3) Then
Set docTarget = Me.getCurrentDatabase().getDocumentById(Source.CaretNoteID)
Forall columnName In Colprogname
If Not (Left(columnName,1) = "$") Then
Call docTarget.ReplaceItemValue(columnName, Columnvalue(columnId))
End If
Let columnId = columnId + 1
End Forall
Call docTarget.Save(True, True, True)
End If
End Sub
End Class
So, if I instantiate an ExampleViewBinder and call attachEvent("inviewedit", uidoc), it should bind Inviewedit to the same event for the uidoc handle... which it does, but it binds the empty Sub defined in ViewEventBinder, not the overridden method in ExampleViewBinder. So when I trigger the event with debugging enabled, I see it calling the abstract function, not its overridden definition. In theory, when attachEvent is called, it should be calling ExampleViewBinder's inherited definition for that method; likewise, when that method calls bindEvent, it should be calling the inherited definition, which should bind to the overridden Inviewedit Sub. But it doesn't. So, if any of you are still here after all this blather, please tell me what I'm missing.








Comments
Posted by Julian Robichaux At 00:07:46 On 07/31/2008 | - Website - |
Posted by Tim Tripcony At 02:35:54 On 07/31/2008 | - Website - |
Then we have to get things to start climbing the support escalation change if this really needs a PMR. (And it sounds like it does.)
Posted by Nathan T. Freeman At 08:15:59 On 07/31/2008 | - Website - |
Sort of like how you can't show images in email for known senders and block for everyone else, it all or nothing.
In any case, thank you for a really great post. It's not often that we get to see "other" language features exposed and explored in this way.
Posted by Wayne At 08:19:32 On 07/31/2008 | - Website - |
Note: I would also suggest using a different method name than InViewEdit. I have found naming my internal method the same name as the Notes event can cause problems.
Hope this helps...
Posted by Peter Presnell At 11:59:29 On 07/31/2008 | - Website - |
Thanks for the writeup, by the way. These advanced things and their shortcomings are severely lacking in coverage.
Posted by Charles Robinson At 13:55:36 On 07/31/2008 | - Website - |
Posted by Nathan T. Freeman At 14:06:42 On 07/31/2008 | - Website - |