Contents Next

Using Overlays

Because we usually like to avoid putting an entire app into one file

About Overlays

When a XUL application gets to be large and full of features, the length of the markup defining its user interface can become extremely long and complicated. The menubar alone of a complete application is often more than 500 lines or code.
500 lines of code may seem like a lot for a menu, and some of you may be wondering at this point if XUL is really all that great after all. Let me assure you that it is. I'm talking here about a menu with two or three hundred (or more) menuitems in it. Even at one line of code per menuitem, a normal sized application menu takes up a lot of code. XUL is actually more compact than most other systems I know of. The number just sounds big because you don't realize how big normal sized application menus really are. Try counting the menuitems in the web browser you are currently using to see what I'm talking about.
Combine that with a toolbar or two, a statusbar, and some actual content, and you have a XUL file that is almost impossible to maintain. The XUL creators realized early on that there needed to be a way to split up a XUL file into smaller, more manageable sized chunks, would be combined at run-time to create the complete application. Thus were born overlays, which solve that problem and several others.
Originally, the functionality now performed by overlays was achieved through a method called XUL Fragments, patterned after the XML fragments system currently under development. Soon however, it became apparent that XUL fragments weren't working out; they never quite did what they were designed to do, and were being used for things that they weren't designed to do. So, XUL fragments were dumped in favor of overlays, which work wonderfully.

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.

Example 4.2.1
Menu Overlay File
<?xml version="1.0"?>

<!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:

Example 4.2.2
Modifications to Main XUL File
<?xml version="1.0"?>
<?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 Tools and Window 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:

Example 4.3.1
Main XUL File Additions
...

<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.

Example 4.3.2
Additions to Menu Overlay File
<?xml version="1.0"?>

<?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 Edit menu (one menuitem at a time) followed by the Tools menu, the Window menu, and the Help menu. (The Tools menu used to be called Tasks, 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 Tools, Window, and Help menus, along with a working Edit 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 Window 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.

Example 4.4.1
Global Dynamic Overlay File
<?xml version="1.0"?>

<!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 Window 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:

Example 4.4.2
Content Content Manifies File
<?xml version="1.0"?>

<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.

Example 4.4.3
Dynamic Overlay DTD File
<!ENTITY menu-tasks-xulnote:label  "XulNote Text Editor">

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.

Contents Next

Copyright © 2002 XulPlanet.com