SnTT: PIM Slap - tab-based frame navigation
If you haven't already seen Chris Blatnick's recent post about universal toolbars, now would be a good time to take a gander. Similarly, if you aren't familiar with Nathan's embedded frameset technique, you may want to check that out as well.
I've been working on an application that, like many Notes apps, contains about 20 views. Okay, point taken: unlike most Notes apps, it only contains about 20 views. But rather than using the traditional hierarchical outline, I decided to try breaking the interface up into visual sections: the outline on the left only contained 5 links, each to an "area" of the application. Each link would load a form that contains a tabbed-table, with each tab containing an embedded view. I demo'ed this interface to the client, and they liked it - it gives them a bit more visual context than the usual "cram a link to every view into the same outline" approach. Particularly because I can toss into the upper right corner of each "area" form a 32x32 version of the 16x16 icon for the outline entry that loads that area. Ties the whole interface together rather nicely, I feel.
This was all well and good until a last-minute requirement implied a need for the last tab of the last area to contain a frameset rather than just a view. No problem, I thought, I'll just embed it using Nathan's approach... I know that works. But once I had, I encountered a limitation I hadn't seen before: since this application also functions as a mail-in database, the reason I wanted a frameset was to allow the users to view the application's Inbox, but also create custom folders for organizing messages the application receives. Despite setting the correct target frame in every location I could think of (frame properties for the folder outline frame, outline properties for the embedded folder outline, even the outline entries themselves), no matter what, clicking on a folder name would launch a new tab instead of replacing the current folder with the new folder in the correct frame.
I ended up using an entirely different approach. This area of the application is now a frameset instead of a form. The top frame contains the form with the tabbed table, but the tabs no longer contain the corresponding embedded views and framesets... just one field each, named predictably: "launchFrameset_X" ( where "X" is the suffix of the frameset associated with the tab; i.e. "frameset-Mail" is associated with the tab containing the field "launchFrameset_Mail" ). The bottom frame contains the "default" view - the one associated with the first tab. Finally (as far as interface is concerned), the frameset is borderless, and the top frame's height is set to a fixed pixel count to obscure everything below the top border of the tabbed table; the result is that it looks identical to how it did before, except that now the default view not only fills the space horizontally, but can fill the available vertical space as well. So far, so good? To get a feel for what this looks like, here's a screenshot of a separate example database I created (which I call "PIM Slap"... I know, always pushing the boundaries of good taste) that uses this technique (click to embiggen):

So we end up with an interface that crams all of our PIM (plus the Notes Welcome page and an internal frameset stolen from Blatnick) into a single tab-driven context. This whole mess can then be nested into a larger frameset - in the case of the application I was previously describing, a frameset like this one is launched via an outline entry associated with its contents.
Finally, to explain how switching tabs actually changes the content of another frame. It's actually fairly straightforward, but I'd never seen this approach used before, so it was a pleasant surprise when the idea dawned on me: when clicking a tab in a form, if that tab contains at least one editable field, the first one Notes "finds" (i.e. not hidden or disabled) gets auto-focused... onFocus is a field event... we can bind to field events. Each tab's field's onFocus event calls a LotusScript function defined in the globals of the form:
Function switchFrame ()
Dim targetFrameset As String
If (formLoaded) Then
Let targetFrameset = "frameset-" & Strright(workspace.CurrentDocument.CurrentField, "launchFrame_")
Call workspace.SetTargetFrame("frame-Target")
Call workspace.OpenFrameSet(targetFrameset)
End If
End Function
NOTE: formLoaded is just a Boolean we toggle to True in the postOpen of the form; it seems that, as the form is loading, the first tab is already selected, so its field gets the focus, which triggers the event, but the target frame hasn't been rendered yet, so Notes doesn't know it exists. As a result, we can't allow this event to target the other frame until we know the form has already fully loaded... which is why we load the default view in the bottom frame; we can thus skip loading it via the tab field for now.
In summary, when any tab is clicked, the form auto-focuses the field that tab contains, its onFocus event is fired, which determines which field has focus, what its name is, and therefore what content to load into the bottom frame. As long as all frame names are unique, this allows for fairly deep nesting without any unpleasant side effects. Nothing too revolutionary, but again, I'd never seen this approach used before, so I wanted to share in case anyone else finds this useful.
The example database is available for download.
|
SnTT: PIM Slap - tab-based frame navigation
|