Workaround for LotusScript event binding
As a followup to last night's post, after receiving some helpful advice from both Peter Presnell and the Batman to my Robin, I found a workaround. I think it's crazy sexy cool.
To very briefly recap, here's the problem:
- Under normal class inheritance circumstances, calling a class method will execute the code at the youngest level (i.e. child class vs. parent) where a method definition can be found.
- When a class method binds an event to another method in the same class, it's binding the definition of that method within the same class. So if a class inherits from another that is binding events and does not override the method doing the binding, the event will be bound to the parent's copy of the target method, even if the child class overrides the bound method.
And here's the workaround:
Within the previously abstract definition of the event handler, we issue a call to itself, but with two subtle additions:
- We use the Me keyword to identify the method. Designer Help implies that this forces the method call to refer to a current class member, but that's not entirely accurate: it actually forces a reference to a current object member. In other words, even though the method being bound to the event is in a parent class, when it's executed it still knows it's inside the scope of an object typed to a child class, so calling Me.whatever executes the same method as defined in that object's class, not its parent class.
- We do a stack trace check to make sure that we don't cause infinite recursion. You see, if the bound method isn't overridden in the child class, Me.whatever only exists in the parent class, so it would keep calling itself over and over... stack overflow, Notes go boom.
For example:
Private Sub Inviewedit(Source As Notesuiview, Requesttype As Integer, Colprogname As Variant, Columnvalue As Variant, Continue As Variant)
If Not(Me.isRecursive(Fulltrim(Split(Lsi_info(14), Chr(10))))) Then
Call Me.inviewEdit(Source, Requesttype, Colprogname, Columnvalue, Continue)
End If
End Sub The isRecursive method is actually defined at the very top level:
Private Function isRecursive (stackTrace As Variant) As Boolean
Dim stackCount As Integer
Let stackCount = Ubound(stackTrace)
If (stackCount > 0) Then
Let Me.isRecursive = (stackTrace(stackCount) = stackTrace(stackCount - 1))
End If
End Function The end result is a framework in which you can create a derived class that overrides only the events you wish to bind, then attaches to those events, either from within the class itself or via calls to its inherited attachEvent method:
Public Class ExampleDocumentBinder As DocumentEventBinder
Public Sub New (Source As NotesUIDocument)
Call Me.attachEvent("Querysave", Source)
End Sub
Private Sub Querysave(Source As Notesuidocument, Continue As Variant)
Dim newEdit As String
Dim editType As String
If (Source.IsNewDoc) Then
Let editType = "created"
Else
Let editType = "modified"
End If
Let newEdit = Cstr(Now) & " - Document " & editType & " by " & Me.getSession().commonUserName
Call Source.Document.ReplaceItemValue("AuditTrail", Fulltrim(Split(newEdit & Chr(13) &_
Join(Source.Document.GetItemValue("AuditTrail"), Chr(13)), Chr(13))))
End Sub
End Class When calling attachEvent, you can pass a single event name, a comma-delimited list of events, or an Array of event names. The second parameter is the object whose event you're binding; if you pass Nothing, it will use the object initially passed to the constructor. The entire framework (along with a few examples) is available for download.
By the way, you might have noticed that this workaround uses the infamous Lsi_info. This undocumented function is almost universally considered unsafe, but is generally acceptable to use in an unthreaded context... so you wouldn't want to use this technique in web agents that could be running simultaneously. But as you've no doubt also noticed, event binding is entirely specific to Notes UI contexts (with the exception of NotesXMLProcessor descendants... and I think we'll leave tackling SAX parser event binding for another day), so I'm not too nervous about using Lsi_info for this niche purpose. About an hour ago, Nathan mentioned he's been playing with CodeLock as a way to make this even safer, so you might be seeing further revision to this model post haste (On Event PostHaste From... tee hee, just kidding).
|
Workaround for LotusScript event binding
|