Blogs

  • Browse Blogs
  • My Blog
  • My Updates

Tags Help

  • View as cloud  | list

Similar Entries

photo

Compile Problem fixe...

Blog:  Jan Schulz
Jan Schulz
Updated 
No Ratings 0     No Comments 0
photo

Sending Designer int...

Blog:  Jan Schulz
Jan Schulz
Updated 
No Ratings 0     Comments 1
photo

I was wrong: plus is...

Blog:  Yellow is the...
Tim Tripcony
Updated 
No Ratings 0     Comments 2
photo

Recompile in 8.0.2

Blog:  Urs Meli
Urs Meli
Updated 
No Ratings 0     No Comments 0
photo

How to test if an ar...

Blog:  Jan Schulz
Jan Schulz
Updated 
No Ratings 0     Comments 1

Dogear Bookmarks

Yellow is the New Blog

Blog Authors:  Tim Tripcony  

Previous |  Main  | Next

Possible bug in LotusScript event binding

Tim Tripcony  |     |  Tags:  lotusscript  |  Comments (0)
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.

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

Previous |  Main  | Next
Skip to main content link. Accesskey S
IBM Lotus Connections Help Tools About

Tags

A tag is a keyword that is used to categorize an entry. To view the entries with a particular tag, click a tag name or enter a tag in the box.
The tag cloud indicates the frequency of tag use. Popular tags appear darkest. The slider control adjusts how many tags are displayed in the tag cloud.