Search

Top Ten List

Of all my ramblings, people seem to find the following the most interesting:
  1. Pimp My Fields
  2. Using the WebBrowser control to view attachments inline
  3. Purty charts in Domino
  4. My approach to DbLookup and DbColumn in Javascript
  5. Mind Map of Database Design
  6. Design Catalog - version control for Domino
  7. Workaround for LotusScript event binding
  8. Clickable URL's in Notes view columns
  9. Every time you use window.open, God kills a kitten
  10. Create, edit, and delete without agents via AJAX

« Possible bug in LotusScript event binding - part 1 | Main| Workaround for LotusScript event binding »

Possible bug in LotusScript event binding - part 2

Category lotusscript
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

Gravatar Image1 - Does it do the same thing if the methods are Public instead of Private?

Gravatar Image2 - Indeed.

Gravatar Image3 - So a) let's have a small downloadable NSF that illustrates the problem and b) when Andre Guirard gets on the BY IM later today, make sure you ping him and ask him to have a quick look. But have the downloadable NSF ready to minimize the demand on his time. Emoticon

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.)

Gravatar Image4 - I don't know that it is a bug. I think what we have here is "incomplete implementation" or "it's working as designed".
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.

Gravatar Image5 - You are correct that when the event is bound it binds it to the method present in the class where the binding takes place. I am not sure if this is as designed or a bug. To resolve the issue you need to override the bindevent method in the ExampleViewEvent class. It doesn't need to be a full implementation, just the bits relevant to that class.
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...

Gravatar Image6 - You lost me several times along the way, but when I finally got to the code I knew where you were going. I ran into this, too, but I never tried overriding the events like Peter suggests. By that time I was exhausted. It's pretty frustrating when you go down a long and winding path and you overcome numerous obstacles only to hit a wall where you're left scratching your head going "wtf?" I just chalked it up as a "working as (poorly) designed" feature and went on my way.

Thanks for the writeup, by the way. These advanced things and their shortcomings are severely lacking in coverage. Emoticon It could be made a little more approachable but I'm glad to see it in any form.

Gravatar Image7 - Oh just wait 'til Tim writes up our solution! Emoticon

Post A Comment

:-D:-o:-p:-x:-(:-):-\:angry::cool::cry::emb::grin::huh::laugh::lips::rolleyes:;-)

Contact Me

Hire Me

Elsewhere

What the Quote?

"I'm tired. And the music is absurd."

Laura Tripcony

"You're like Freulein Maria: you've gotten out of the habit."

Laura Tripcony

"I think the air shark would be nice to us, 'cause we freed him from the burrito."

Tim Tripcony

"There's always room for cello."

Laura Hearron

"It involves a flank, a beachhead, and a claymore. Solve for X."

Steven Rodgers

Apparel

Lotus Rocks

I write the code that makes the young girls cry

Current Terror Alert Level

Assorted Linkage

ClustrMap