Commands
A command is an operation which may be invoked.
Command Elements
The command element is used to create commands which can be used to carry out operations. You don't need to use commands, since you can just call a script to handle things. However, a command has the advantage that it can be disabled automatically when needed and can be invoked externally without needing to know about the details of its implementation. Commands provide a suitable way to abstract out operations from the code. Commands become useful for larger applications.
For instance, in order to implement the clipboard menu commands, cut, copy and paste, you can use commands. If you did not use commands, you would need to figure out which field has the focus, then check to ensure that the operation is suitable for that element. In addition, the menu commands would need to be enabled and disabled depending on whether the focused element had selected text or not, and for paste operations, whether there is something suitable on the clipboard to paste. As you can see, this becomes complicated. By using commands, much of the work is handled for you.
You can use a command for any operation. Mozilla uses them for almost every menu command. In addition, text fields and other widgets have a number of commands which they already support that you can invoke. You should use them when the operation depends on which element is focused.
A command is identified by its id attribute. Mozilla uses the convention that command id's start with 'cmd_'. You will probably want to use the same id if a command is already being used, however, for your own commands, you can use any command id you wish. To avoid conflicts, you may wish to include the application name in the command id. A simple way of using commands is as follows:
Example 6.5.1: Source View<command id="cmd_openhelp" oncommand="alert('Help!');"/> <button label="Help" command="cmd_openhelp"/>
In this example, instead of placing the oncommand attribute on the button, we instead place it on a command element. The two are then linked using the command attribute, which has the value of the command's id. The result is that when the button is pressed, the command is invoked.
There are two advantages to using this approach. First, it moves all your operations onto commands which can all be grouped together in one section of the XUL file. This means that code is all together and not scattered throughout the UI code. The other advantage is that several buttons or other UI elements can be hooked up to the same command. For instance, you might have a menu item, a toolbar button and a keyboard shortuct all for the same operation. Rather than repeat the code three times, you can hook all three up to the same command. Normally, you would only hook up elements that would send a command event.
If you set the disabled attribute on the command, the command will be disabled and it will not be invoked. In addition, any buttons and menu items hooked up to it will be disabled automatically. If you re-enable the command, the buttons will become enabled again.
Example 6.5.2: Source View<command id="cmd_openhelp" oncommand="alert('Help');"/> <button label="Help" command="cmd_openhelp"/> <button label="More Help" command="cmd_openhelp"/> <button label="Disable" oncommand="document.getElementById('cmd_openhelp').setAttribute('disabled','true');"/> <button label="Enable" oncommand="document.getElementById('cmd_openhelp').removeAttribute('disabled');"/>
In this example, both buttons use the same command. When the Disable button is pressed, the command is disabled by setting its disabled attribute, and both buttons will be disabled as well.
It is normal to put a group of commands inside a commandset element, together near the top of the XUL file, as in the following:
<commandset> <command id="cmd_open" oncommand="alert('Open!');"/> <command id="cmd_help" oncommand="alert('Help!');"/> </commandset>
A command is invoked when the user activates the button or other element attached to the command. You can also invoke a command by calling the doCommand method either of the command element or an element attached to the command such as a button.
Command Dispatching
You can also use commands without using command elements, or at least, without adding a oncommand attribute to the command. In this case, the command will not invoke a script directly, but instead, find an element or function which will handle the command. This function may be separate from the XUL itself, and might be handled internally by a widget. In order to find something to handle the command, XUL uses an object called a command dispatcher. This object locates a handler for a command. A handler for a command is called a controller. So, essentially, when a command is invoked, the command dispatcher locates a controller which can handle the command. You can think of the command element as a type of controller for the command.
The command dispatcher locates a controller by looking at the currently focused element to see if it has a controller which can handle the command. XUL elements have a controllers property which is used to check. You can use the controllers property to add your own controllers. You might use this to have a listbox respond to cut, copy and paste operations, for instance. An example of this will be provided later. By default, only textboxes have a controller that does anything. The textbox controller handles clipboard operations, selection, undo and redo as well as some editing operations. Note that an element may have multiple controllers, which will all be checked.
If the currently focused element does not have a suitable controller, the window is checked next. The window also has a controllers property which you can modify if desired. If the focus is inside a frame, each frame leading to the top-level window is checked as as well. This means that commands will work even if the focus is inside a frame. This works well for a browser, since editing commands invoked from the main menu will work inside the content area. Note that HTML also has a commands and controller system although you can't use it on unprivileged web pages, but you may use it from, for example, a browser extension. If the window doesn't provide a controller capable of handling the command, nothing will happen.
You can get the command dispatcher using the document's commandDispatcher property, or you can retrieve it from the controllers list on an element or a window. The command dispatcher contains methods for retrieving controllers for commands and for retrieving and modifying the focus.
You can implement your own controllers to respond to commands. You could even override the default handling of a command with careful placement of the controller. A controller is expected to implement four methods, which are listed below:
- supportsCommand (command): this method should return true if the controller supports a command. If you return false, the command is not handled and command dispatcher will look for another controller. A single controller may support multiple commands.
- isCommandEnabled (command): this method should return true if the command is enabled, or false if it is disabled. Corresponding buttons will be disabled automatically.
- doCommand (command): execute the command. This is where you would put the code to handle the command.
- onEvent (event): this method handles an event.
Let's assume that we want to implement a listbox that handles the delete command. When the user selects Delete from the menu, the listbox deletes the selected row. In this case, you just need to attach a controller to a listbox which will perform the action.doCommand method
Try opening the example below in a browser window and selecting items from the list. You'll notice that the Delete command on the browser's Edit menu is enabled and that selecting it will delete a row. The example below isn't completely polished. Really, we should ensure that the selection and focus is adjusted appropriately after a deletion.
<window id="controller-example" title="Controller Example" onload="init();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <script> function init() { var list = document.getElementById("theList"); var listController = { supportsCommand : function(cmd){ return (cmd == "cmd_delete"); }, isCommandEnabled : function(cmd){ if (cmd == "cmd_delete") return (list.selectedItem != null); return false; }, doCommand : function(cmd){ list.removeItemAt(list.selectedIndex); }, onEvent : function(evt){ } }; list.controllers.appendController(listController); } </script> <listbox id="theList"> <listitem label="Ocean"/> <listitem label="Desert"/> <listitem label="Jungle"/> <listitem label="Swamp"/> </listbox> </window>
The controller (listController) implements the four methods described above. The supportsCommand method returns true for the 'cmd_delete' command, which is the name of the command used when the Delete menu item is selected. For other commands, false should be returned since the controller does not handle any other commands. If you wanted to handle more commands, check for them here, since you will often use a single controller for multiple related commands.
The isCommandEnabled method returns true if the command should be enabled. In this case, we check if there is a selected item in the listbox and return true if there is. If there is no selection, false is returned. If you delete all the rows in the example, the Delete command will become disabled. You may have to click the listbox to update the menu in this simple example. The doCommand method will be called when the Delete menu item is selected, and this will cause the selected row in the listbox to be deleted. Nothing needs to happen for the onEvent method, so no code is added for this method.
We attach this controller to the listbox by calling the appendController method of the listbox's controllers. The controller object has a number of methods that may be used to manipulate the controllers. For instance, there is also an insertControllerAt method which inserts a controller into an element before other ones. This might be useful to override commands. For example, the following example will disable pasting into a textbox.
var tboxController = { supportsCommand : function(cmd){ return (cmd == "cmd_paste"); }, isCommandEnabled : function(cmd){ return false; }, doCommand : function(cmd){ }, onEvent : function(evt){ } }; document.getElementById("tbox").controllers.insertControllerAt(0,tboxController);
In this example, we insert the controller at index 0, which means before any others. The new controller supports the 'cmd_paste' command and always indicates that the command is disabled. The default textbox controller never gets called because the command dispatcher found the controller above to handle the command first.
(Next) Next, we'll find out how to update commands.