You are currently viewing a snapshot of www.mozilla.org taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to www.mozilla.org, please file a bug.



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.