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

« Movie Lineup Fail | Main| why most box office figures are complete nonsense »

One last thing (for now) about event binding: delegate functions

Category lotusscript
Thanks for all the feedback on my recent ramblings regarding LotusScript event binding. Just one more post on this and I'll leave it alone for a while.

Both Thomas Bahn and Devin Olson chimed in on the last article in this series, and Peter Presnell posted a great article about compensating for weaknesses in LotusScript's OOP model, all of which prompted some useful mods to the framework. If you've already downloaded it, you may want to snag a fresh copy.

Thomas suggested that, instead of calling each bound Sub recursively with stack trace checking to prevent infinite recursion in cases where the event handler has not been overridden in the derived class... just bind the event to a delegate function that, in turn, calls the actual event handler. This results in a bit more code within the framework classes, but since the structure is such that you won't need to modify those classes again until new events are added in future versions of Notes, I'm perfectly fine with that as a tradeoff for avoiding recursion altogether. Don't worry, we'll find another excuse to use Nathan's code locking approach.

Devin's comments and Peter's article both provided insight into forcing (at runtime, at least) methods and classes to be treated as abstract, and, as such, overridden and derived, respectively. That was a bit of a mouthful, so perhaps it would be simpler to just show you the most basic of the EventBinder child classes in its current form:

Public Class TimerEventBinder As EventBinder
    
    Public Sub New (Source As NotesTimer)
        Dim classname As String
        
        Let className = Typename(Me)
        If (className = "TIMEREVENTBINDER") Then
            Error ERR_ABSTRACT_INSTANTIATION, MSG_ABSTRACT_INSTANTIATION & classname
            Exit Sub
        End If
        Set Me.context = Source
    End Sub
    
    Private Function bindEvent (Byval eventName As String, Source As Variant)
        Select Case Lcase(eventName)
        Case "alarm":
            On Event Alarm From Source Call delegate_Alarm
        Case Else:
            Error ERR_UNSUPPORTED_EVENT, MSG_UNSUPPORTED_EVENT & Ucase(eventName) & " in class " & Typename(Me)
        End Select
    End Function
    
    Private Sub delegate_Alarm ( Source As NotesTimer )
        Call Me.Alarm(Source)
    End Sub
    
    Private Sub Alarm( Source As NotesTimer )
        If (OPTION_FORCE_METHOD_OVERRIDE) Then
            Error ERR_ABSTRACT_METHOD, MSG_ABSTRACT_METHOD & Typename(Me)
        End If
    End Sub
    
End Class


The end result is a class that cannot be instantiated; to use its functionality, you must create a class that inherits from it. Similarly, in order to attach to the Alarm event, you must override the Alarm Sub with the same method signature (although there's a Const in the framework library that you can change to False to allow silent failure, but if you're not overriding the method, your event will do nothing, so you probably want an error thrown to notify you of the omission). Finally, another error is thrown if you try to attach to an event that doesn't even exist.

All in all, I think this has become a pretty solid framework, allowing you to bind UI events within an OOP context in a straightforward manner. Let me know if you have any additional suggestions for how this should be structured or examples of where it would be particularly useful.

Comments

Gravatar Image1 - Hm, "delegate functions" sounds strage Emoticon

How about something like this: Each Event handler gets it's own class: only new() + the required event handling method -> On abstract class and a actual implementation overwrites the only method or adds configuration methods (think: .activate() and such things)

In the controller new() you bind a Controler:method() to each event. This Methods just loop over a list (per event) and call a method on each object. You also add a call to a private setUpEventHandlers(), which sets up the basic event handlers and adds them to the lists.

If you want to extend the functionality, you just need to extend the BaseController and add your own EventHandler to the list or you can remove one or all by overwriting the setUpEventHandlers().

You end up with a BaseModel (does validation, etc), a BaseController, which binds to the events of the NotesUI* and delegates the actual event handling to the EventHandlers. You would need one abstract eventhandler per Event and one or more actual implementations.

If you have a model - view - controller Framework (see { Link } -> just get the example DB) you should be able to reuse most of the EventHandler and just extend the BaseController and BaseModel.

And all without "doubled" methods Emoticon

Gravatar Image2 - Here is the code for this idea: { Link }

Gravatar Image3 - Is there a way to bind the NotesUIDocument.QueryOpen and have it triggered?

I see that you have it written a way to bind the QueryOpen, but as far as I can tell, the earliest possible time to create a DocumentEventBinder is within the QueryOpen, at which point it's already been called, so it's a useless bind, right?

I'm hoping I'm missing something, but maybe QueryOpen events should be taken out of your framework, and comments should warn that PostOpen won't work either unless the EventBinder is created within the QueryOpen?

Gravatar Image4 - @Jan - interesting stuff, I'll take a closer look when the dust settles from my move.

@John - no, you're right on the mark: at its core, event binding is all about timing, so practically speaking you can't bind to the QueryOpen, because you need a handle on a UI object in order to bind to its events, and the handle can't be obtained until it's already open... in other words, the QueryOpen has already run, so while technically you can still bind to it, the event won't be triggered again for that object, so it's too late for that binding to be of any practical use. When I originally wrote the classes I omitted the QueryOpen events for that very reason, but prior to posting the first article about this, I added them in at the last minute for consistency. I probably will yank them back out and add an explanatory comment.

Even the PostOpen has weird timing issues: if I bind to it from within the QueryOpen on a form, for example, the bound handler is executed as intended if I'm running debugger (see Jan's post for information on a related quirk); without debugger the event is never bound. LotusScript seems to create some strange scope overlap when debugger is enabled, and I suspect that actually allows functionality like this that otherwise just doesn't work as expected. Nathan's "Revolution" technique takes advantage of another approach to creating scope overlap: namely, combining event binding with Execute statements, which does a runtime compile, allowing global variables in disparate memory scopes to be assigned pointers to the same object... freaky stuff, but allows for some rather cool cross-contextual interaction.

My hope is that IBM will eventually provide some way of establishing a handle on UI objects that aren't open yet. I know that probably sounds backwards, because the term "UI" automatically implies that this is something the user "sees", and since the user can't "see" it until it's already open, it's technically not a UI object until then... but rigid adherence to that premise would dictate that a QueryOpen event wouldn't even exist. Since it does, we should be able to bind to it, and it bugs me that we can only apply this model to a subset of each object's events.

Gravatar Image5 - Agreed. The "Me" keyword should work within the Initialize of UI objects. I won't hold my breath, though.

Can you give me more info about the PostOpen issue, like what client you're using? The following works perfectly for me. Does it work for you?

Use "Class:DocumentEventBinder"
Public Class NonEditableDocumentEventBinder As DocumentEventBinder
'Written by John Smart 7-Aug-2008
'This object must be instantiated within the QueryOpen, else PostOpen will be bound too late for it to be triggered.

Sub New (Source As NotesUIDocument)
bindEvent "PostOpen", Source
bindEvent "QueryModeChange", Source
End Sub


Private Sub PostOpen(Source As NotesUIDocument)
If Source.IsNewDoc Then
Call Source.Close
Messagebox "You are not allowed to create this document manually."
Elseif Source.EditMode Then
Source.EditMode = False
End If
End Sub


Private Sub Querymodechange(Source As Notesuidocument, Continue As Variant)
If Not Source.EditMode Then Continue = False
End Sub
End Class

Dim oBinder As NonEditableDocumentEventBinder

Sub Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant)
Set oBinder = New NonEditableDocumentEventBinder(Source)
End Sub

Post A Comment

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

Contact Me

Hire Me

Elsewhere

What the Quote?

"Elvis's belt is the shiniest thing on 'im."

Laura Tripcony

"...at least the children we don't have are wearing pants."

Tim Tripcony

"Oh, hello, kettle, this is... no, wait... I think you should be pot. You smell more."

Steven Rodgers

"The best thing about Boolean is, even if you're wrong, you're only off by a bit."

John Jaeckle

"Spain used to be all powerful. Now they just nap all the time."

Laura Tripcony

Apparel

Lotus Rocks

I write the code that makes the young girls cry

Current Terror Alert Level

Assorted Linkage

ClustrMap