Using Overlays
Because we usually like to avoid putting an entire app into one fileAbout Overlays
Local Overlays
Although the XUL describing the menu in the XulNote application is not currently very big, it will soon become much larger as we add menuitems that actually do stuff. To prepare for this, we are going to move the code for our menubar into a separate file, and use overlays to combine it with the main XUL file at runtime. Go into your package's content directory, and create a file called menuoverlay.xul. Add to the file the following code.
<!DOCTYPE window [
<!ENTITY % xulnoteDTD SYSTEM "chrome://xulnote/locale/xulnote.dtd" >
%xulnoteDTD;
]>
<overlay id="menuoverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<commandset id="maincommands">
<command id="menu-file-close:command" oncommand="closeWindow();"/>
</commandset>
<keyset id="mainkeys">
<key
id = "menu-file-close:key"
key = "&menu-file-close:key;"
observes = "menu-file-close:command"
modifiers = "accel" />
</keyset>
<menubar id="menu">
<menu id="menu-file" label="&menu-file:label;" accesskey="&menu-file:accesskey;">
<menupopup id="menu-file-popup">
<menuitem
id = "menu-file-close"
key = "menu-file-close:key"
label = "&menu-file-close:label;"
command = "menu-file-close:command"
accesskey = "&menu-file-close:accesskey;"/>
</menupopup>
</menu>
</menubar>
</overlay>
This creates a new XUL file containing nothing but our menu code. We now need to remove the menu code from our main XUL file and insert a line of code to import the menu into it instead. Open your xulnote.xul, and make the following modifications:
<?xml-stylesheet href="xulnote.css" type="text/css"?>
<?xul-overlay href="chrome://xulnote/content/menuoverlay.xul"?>
<!DOCTYPE window [
<!ENTITY % xulnoteDTD SYSTEM "chrome://xulnote/locale/xulnote.dtd">
%xulnoteDTD;
]>
<window
title = "&window.title;"
id = "xulnote-main-window"
xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
width = "640"
height = "480"
orient = "vertical"
persist = "screenX screenY width height sizemode">
<script type="application/x-javascript" src="xulnote.js"/>
<keyset id="mainkeys"/>
<commandset id="maincommands"/>
<toolbox id="main-toolbox">
<menubar id="menu"/>
</toolbox>
<textbox id="document" multiline="true" flex="1"/>
</window>
Your application should now run exactly as it did before, only this time the window is described in two short files instead of one not quite so short file. When the program is run, mozilla loads the menu overlay because of the <?xul-overlay?> tag at the top. If any of the elements in the main XUL file have the same id as an element from the overlay, the two will be merged before the window is displayed. In this case the <menubar> in the main XUL file has the same id as the <menubar> in the overlay, so the two are combined to create the menbar that you see when you run the application.
And that's all you will ever need to know about local overlays. I have a zip archive of this section if you want it.
Cross Package Overlays
Another great thing about overlays is that an overlay does not have to be in the same directory, or even the same application, as the XUL file that uses it. This is called "Cross Package Overlays", and is the method by which all the communicator applications have identical and menus. The menus are defined only once, in a file called tasksOverlay.xul, and are used by every application in the mozilla browser suite. Not only does this save disk space, it makes updating and maintaining the menus much easier, as a single change to a menu defined in tasksOverlay.xul is automatically reflected in every app containing it.
If your application is designed to be part of mozilla communicator, as is the XulNote package, you may wish to have some of the global menus in your application as well. If you plan to do so, follow the instructions in the rest of this section.
Go into your xulnote.xul file and add to it the following line of code:
<keyset id="mainkeys"/>
<commandset id="maincommands"/>
<stringbundleset id="mainstrings"/>
<toolbox id="main-toolbox">
<menubar id="menu"/>
</toolbox>
...
Now, edit your menuoverlay file so that it looks like this. It's a lot of code, but it's very useful, as we will see in a minute.
<?xul-overlay href="chrome://global/content/globalOverlay.xul"?>
<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
<?xul-overlay href="chrome://communicator/content/tasksOverlay.xul"?>
<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://global/locale/brand.dtd" >
%brandDTD;
<!ENTITY % xulnoteDTD SYSTEM "chrome://xulnote/locale/xulnote.dtd" >
%xulnoteDTD;
]>
<overlay id="menuoverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<commandset id="maincommands">
<commandset id="globalEditMenuItems"/>
<commandset id="selectEditMenuItems"/>
<commandset id="undoEditMenuItems"/>
<commandset id="clipboardEditMenuItems"/>
<command id="cmd_undo"/>
<command id="cmd_redo"/>
<command id="cmd_cut"/>
<command id="cmd_copy"/>
<command id="cmd_paste"/>
<command id="cmd_delete"/>
<command id="cmd_selectAll"/>
<commandset id="tasksCommands"/>
<command id="menu-file-close:command" oncommand="closeWindow();"/>
</commandset>
<keyset id="mainkeys">
<key id="key_undo"/>
<key id="key_redo"/>
<key id="key_cut"/>
<key id="key_copy"/>
<key id="key_paste"/>
<key id="key_delete"/>
<key id="key_selectAll"/>
<keyset id="tasksKeys"/>
<key
id = "menu-file-close:key"
key = "&menu-file-close:key;"
observes = "menu-file-close:command"
modifiers = "accel" />
</keyset>
<menubar id="menu">
<menu id="menu-file" label="&menu-file:label;" accesskey="&menu-file:accesskey;">
<menupopup id="menu-file-popup">
<menuitem
id = "menu-file-close"
key = "menu-file-close:key"
label = "&menu-file-close:label;"
command = "menu-file-close:command"
accesskey = "&menu-file-close:accesskey;"/>
</menupopup>
</menu>
<menu id="menu_Edit">
<menupopup>
<menuitem id="menu_undo"/>
<menuitem id="menu_redo"/>
<menuseparator/>
<menuitem id="menu_cut"/>
<menuitem id="menu_copy"/>
<menuitem id="menu_paste"/>
<menuitem id="menu_delete"/>
<menuseparator/>
<menuitem id="menu_selectAll"/>
</menupopup>
</menu>
<menu id="tasksMenu"/>
<menu id="windowMenu"/>
<menu id="menu_Help"/>
</menubar>
</overlay>
You can see at the bottom of the file where we first import the menu (one menuitem at a time) followed by the menu, the menu, and the menu. (The menu used to be called , and they never changed the id, so we have to import it using the word tasks.)
Save the file and run your application. Unless one of us messed up somewhere, your application should contain the standard communicator , , and menus, along with a working menu. This saves us a lot of work, as accessing the XPCOM interfaces required to perform clipboard operations is a complicated procedure.
If that was too much file editing for your tastes, you can use the zip archive instead.
Dynamic Overlays
The overlays discussed so far have been what is called "explicit overlays" meaning that the main XUL file specifies which overlays it wants to use and which elements it wants to import. There is another kind of overlays, called "dynamic overlays", in which the overlay itself specifies which file it wants to overlay. Dynamic overlays can be used to overlay another file without making any changes to the first file, because the main XUL file essentially doesn't even know that there is an overlay being applied to it. We will use this procedure to add our application to communicator's menu, so that we, and our end users, won't have to load it using a command line parameter.
Return to your content directory, if you are not already there, and create a file called xulnoteOverlay.xul, with the following code inside.
<!DOCTYPE window SYSTEM "chrome://xulnote/locale/xulnoteOverlay.dtd">
<overlay id="xulnoteOverlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript">
function runXulNote()
{
window.openDialog('chrome://xulnote/content/', '_blank', 'chrome,dialog=no');
}
</script>
<menupopup id="windowPopup">
<menuitem label="&menu-tasks-xulnote:label;" oncommand="runXulNote();" insertbefore="sep-window-list"/>
</menupopup>
</overlay>
Because the id of the <menupopup> tag in the above file is the same as the id of the <menupopup> from the menu in the task menu overlay, our new menuitem will be added to that menu whenever a package imports it. The insertbefore attribute tells mozilla to insert out new menuitem right before the separator in that menu, which puts us right at the end of the program list, which is exactly where we want to be. If you wish to add your application to another menu, use the id of the menupopup from that menu for the id of your menupopup in this file, and the name of the file where that menu is found when you midify contents.rdf below.
The next thing we need to do is tell mozilla to apply our overlay to the menus in the tasks menu overlay file. To do that, we just need to modify the contents.rdf file from our content directory such that it looks like this:
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
<RDF:Seq about="urn:mozilla:package:root">
<RDF:li resource="urn:mozilla:package:xulnote"/>
</RDF:Seq>
<RDF:Description about="urn:mozilla:package:xulnote"
chrome:displayName="XulNote Text Editor"
chrome:author="Aaron Andersen"
chrome:name="xulnote">
</RDF:Description>
<RDF:Seq about="urn:mozilla:overlays">
<RDF:li resource="chrome://communicator/content/tasksOverlay.xul"/>
</RDF:Seq>
<RDF:Seq about="chrome://communicator/content/tasksOverlay.xul">
<RDF:li>chrome://xulnote/content/xulnoteOverlay.xul</RDF:li>
</RDF:Seq>
</RDF:RDF>
These changes tell mozilla that we want to dynamically overlay the file tasksOverlay.xul with our file xulnoteOverlay.xul. Note that we are actually using our overlay to add content to another overlay, which in turn will be added to the communicator apps whenever one of them is used.
Before we test our new overlay, we need to create one more file, a dtd file, which will contain the strings from our overlay. The file should be called xulnoteOverlay.dtd, should be created in your locale directory, and should contain the following code.
Mozilla keeps a list of all registered dynamic overlays in a subdirectory of the chrome directory called overlayinfo. Mozilla automatically updates this list whenever a new package is installed. Since our application was installed before our dynamic overlay existed, we need to force mozilla to rebuild the list. The best way to do that is to open your installed-chrome.txt file, add a space and then delete it and save the file. This will reset the last modify date on installed-chrome.txt to today, and then when you start mozilla again the chrome registry will see that the date has been changed, know that we have been messing around in the file, and kindly reprocess it for us, enabling our dynamic overlay when it does.
Now, sit back, get comfortable, and run mozilla. Unless something messed up, you should now see XulNote in your window menu; if you do, you will probably be glad to know that you will never have to access XulNote via a -chrome chrome:// command line parameter again!
As always, you can use the zip archive if you wish.
menuOverlay.xul