Context Nodes and Focus

Draft 0
Last modified on 2/16/99 by Mike Pinkerton

This is a concatenation of some relevant emails and newsgroup postings about command parameter. Hyatt and Rod are behind most of this, blame them. This document was penned by hyatt after midnight, so all bets are off :-).

Overview

If you have not yet read the Broadcaster/Observer document, which describes the replacement for observing changes in appCore state, please do so now and understand the example. The rest of this discussion builds upon that example. To summarize, we are passing parameters to the appCore at the event-handling level. We hook up to a broadcaster to pick up the enabled state, and we call into the appCore by invoking methods through JavaScript.

This document adds the concept of a Context node which works in conjuction with Broadcaster and Observer nodes to track focus and allow the same UI element to perform different actions based on which component (say a bookmarks tree or a mail tree) is currently in focus in a given window.

Handling Focus With Context Nodes

To do focus, we need to have a new kind of node: a Context node (suggested by Rod). We need to know how to do two things with it. We have to (a) change observation relationships based on what has the focus, and (b) do something different based on what has the focus.

XUL Syntax

<incontext element="nodeIdentifier" oncommand="some JavaScript"/>

Example

So how does this work? Like this... pardon any DOM calls that aren't exactly right. Remember that the UI logic seems backwards because the DOM attribute for enabled/disable state is called "disabled."

<window>

...

<broadcasterset>
   <broadcaster id="cantDeleteInMailTree"/>   // Surfaced by the mail AppCore
   <broadcaster id="cantDeleteInBookmarksTree"/> // Surfaced by the bookmarks AppCore
</broadcasterset>

...

<button id="myDeleteButton" onClick="getChildWithAttribute("infocus").doCommand()">
   <incontext element="mailTree" onCommand="mailAppCore.delete()">
      <observes element="cantDeleteInMailTree" attribute="disabled"/>
   </incontext>
   <incontext element="bookmarksTree" onCommand="bookmarksAppCore.deleteBookmarks()">
      <observes element="cantDeleteInBookmarksTree" attribute="disabled"/>
   </incontext>
</button>

...

<tree id="mailTree" onselectionchanged="mailCore.selectionChanged(...)">
   <broadcaster id="cantDeleteInMailTree"/>...</tree>

<tree id="bookmarksTree" onSelectionChanged="bookmarksCore.selectionChanged(...)">
   <broadcaster id="cantDeleteInBookmarksTree"/>...</tree>

...

</window>

So here's how this example works:

First, there are two broadcasters for the enabled state of deletion...one for a mail tree and one for a bookmarks tree. We now have a button on our toolbar that simply says "DELETE", and it wants to do the right thing depending on which tree widget has the focus.

To do this, we need to make it so that the OBSERVES relationship on the button is dynamic based on context. When the focus switches, what the button observes has to change. We do this with context nodes.

Context nodes are children of the actual widget . The observes relationships they contain as children are what should be applied to the actual widget when this context is in focus.

So how does a Context node get notified when the focus changes? We hook up the Context node up as an observer of an "inFocus" attribute on a widget. When a widget in XUL grabs the focus, it will set this inFocus attribute to TRUE on its node, which will result in any and all context nodes that are listening for this attribute getting notified.

A Context node will then know (again, more smarts in our content nodes) to flip a switch and change the broadcasters that its parent widget is observing. Because a context node is listening to the inFocus attribute, it will also set/unset this inFocus attribute on itself when it gets notified.

When a mailTree's selection changes, it calls into the AppCore, and since we have parameters, the tree can pass in the originating widget, the widget's selection (as a range) and its broadcaster node(or nodes). The AppCore then just has to implement a completely generic function that decides, based on the selected items, whether or not the broadcaster node that's passed in should be enabled or disabled.

When a command needs to be executed from the button, you can use the standard DOM APIs to grab the correct context node (by looking for the one that has its inFocus attribute set). You can then call doCommand to make the JavaScript contained inside the onCommand attribute fire.

The one non-obvious part about the syntax in the example above is

<context obsid="mailWidget">

This is shorthand for saying that the context node will observe a broadcaster with id mailWidget and that it will observe the inFocus attribute on that broadcaster.


Last modified by Mike Pinkerton
Send comments to the news group netscape.public.mozilla.xpfe