Focus and Command Dispatching
- Feature Owner
- David Hyatt
In addition to the DOM APIs for tracking focus (namely addEventListener
and removeEventListener
on focus
and
blur
events), XUL documents contain an object called
the focus tracker that monitors the current focused object within the XUL
document. The focus tracker is capable of monitoring focus changes even within subframes
of the document.
The focus tracker is retrieved using the focus
property on the
XUL document. This property is read only. All XUL documents have a single focus tracker,
and this tracker cannot be modified or deleted.
At any given time, the current focused object can be obtained
by asking the focus tracker for its current
property.
This always refers to the innermost object in a focus chain (so if, for example, there
are many nested framesets, the innermost frame with the focus would be the one
stored in this property).
dump("The current focused object is: " + document.focus.current);
Elements in a XUL document can log their interest in listening to focus changes by
registering themselves with the document's focus tracker. The focus tracker supports
two methods, addFocusListener
and removeFocusListener
.
These methods each take a single DOM element as an argument.
Typically a broadcaster node that represents a single action (e.g., Cut
)
will register itself with the focus tracker, and then it can handle passing off any state
changes to UI elements as necessary. (See Broadcasters and
Observers for details.)
<broadcaster id="cut"/> ... document.focus.addFocusListener(document.getElementById('cut'));
The objects that listen to focus are typically actions, and as such, they are concerned with enabling or disabling themselves when the focus changes or when the state within a focused object changes. They are also interested in supplying a common API for the execution of their action, regardless of which node has the focus.
Not yet implemented An object called a controller
determines whether or not an action
should be enabled/disabled for its associated object; it also supplies the interface through which the action can
be invoked.
Any XUL element can have a controller attached to it. The controller can be retrieved through
the controller
property on the XUL element, and it supports
a certain set of interfaces, called Command Sets
. Command sets
can be obtained by calling QueryInterface
on controller objects.
There is a generic command set object that can be used for any parameterless commands. More complicated commands that require parameter-passing, or that need to expose custom methods for determining enabled/disabled state, can be placed inside custom command sets that implement their own scriptable interfaces.
The nsIGenericCommandSet
interface has two simple methods:
DoCommand
, which takes a string that represents the command's name,
and IsCommandEnabled
, which also takes a string that represents
the command's name and returns a boolean value indicating whether or not the command is
enabled.
<broadcaster id="cut" action="performCut();"/> ... function performCut() { var c = document.focus.current.controller; var g = c.QueryInterface(Components.interfaces.nsIGenericCommandSet); g.DoCommand('cut'); }
The focus tracker supplies a convenience method called getController
that can be used as a shorthand for retrieving the focused node's controller and using
QueryInterface to obtain the appropriate command set.
<broadcaster id="cut" action="performCut();"/> ... function performCut() { var c = document.focus.getController(Components.interfaces.nsIGenericCommandSet); c.DoCommand('cut'); }
All controllers must implement the nsIController
interface, which
the focus tracker uses to inform the controller of its focus tracker. The controller can
then, at its discretion, communicate to the focus tracker that its state has changed and
that any observers of the focus tracker may potentially need to update their own states
(e.g., enable or disable themselves).
For example, a tree widget's controller would communicate with the focus tracker whenever
its selection changed. The focus tracker would then notify all of its observers.
It would do so by executing a change
handler that was attached to
the observers of the focus tracker.
In addition to invoking the change handlers when the controllers communicate a state change to the focus tracker, the focus tracker also invokes the handlers automatically whenever the focus changes.
<broadcaster id="cut" action="performCut(); onchange="updateUI('cut', this);"/> ... function updateUI(commandName, element) { var c = document.focus.getController(Components.interfaces.nsIGenericCommandSet); if (c.IsCommandEnabled(commandName)) element.unsetAttribute('disabled'); else element.setAttribute('disabled'); }
Although the nsIGenericCommandSet
works well for any parameterless
commands, other commands might be more complex, and might require custom enabling/disabling
checkers, as well as custom APIs for the invocation of their commands.
An example of such a system might be commands in the Composer package for color management. The application of color to the selection within a focused object requires that the new color be passed in to the command as a parameter.
An interface, nsIColorCommandSet, could contain an applyColor method that takes a string, the RGB value of the color being applied, as a single argument. The code then looks very similar to previous examples, except that the command set being used is a different interface.
<broadcaster id="colorApplier" action="applyColor();"/> ... function applyColor() { // Code that pops up a color picker and stashes a color result in // the variable color result var colorResult = ...; // Execute the command for applying the color. var c = document.focus.getController(Components.interfaces.nsIColorCommandSet); c.ApplyColor(colorResult); }
Open Issues
- Does the focus tracker only need to track elements, or does it also need to worry about windows?
- If it isn't sufficient to only track elements, how do we attach controllers to other DOM objects that might not be XUL?
- How does the controller registration happen for HTML text fields that Ender uses? (It needs to happen automatically when the node is created.) An answer might be that Ender doesn't use controllers, but the getController method of the focus tracker special cases the HTML text nodes.