XPCAM :: Wizard Library

Q & A

What is the Wizard Library?

The Wizard Library is a JavaScript library located in the global content resources folder that contains numerous routines to make creating application wizards easier.

Why was the Wizard Library Created?

Because creating a wizard before involved duplicating large amounts of code, and making many changes writing lots of unnecessary code to get the job done. By using the Wizard Library, you can dramatically reduce the amount of wizard infrastructure coding you have to do.

Where are the files located?

The files of importance are located in chrome://global/content/. Refer to WizardManager.js, wizardOverlay.js and wizardOverlay.xul. widgetStateManager.js is also required (see notes below) You must include all these JS files in the master XUL file of your wizard, and include the wizardOverlay.xul file as an overlay (to provide the buttons). If you want to create your own buttons, see the section on Advanced Topics.

Creating a Wizard

Follow these instructions carefully, and the Wizard Library should work for you.

Create the XUL File

Your XUL File will need to include the JS files described above, and the button overlay. Your content is to be loaded into an IFRAME, which should have a name and id attribute set to the same value. Include a box with id="wizardButtons" so that the buttons are loaded. (See chrome://profile/content/createProfileWizard.xul for a working example of a wizard XUL file built on this library)

Set the onload handler of the window element in the XUL file to call the Startup() function for the dialog, with two parameters. The first is the tag name of the first panel that you wish to load, the second is the id/name of the frame in which you will load content. The tag name is the file name of the document without the file path or extension. e.g. chrome://*/newProfile1_2.xul's tag would be "newProfile1_2".
e.g. onload="Startup('newProfile1_1','content');"

Create the JS File

Of course, you will still have to write some JavaScript. Create a JS file for your wizard, and include it in the XUL.

Create a wizardManager variable, and initialise to null (if you choose).

Create a wizardMap object, which must contain the navigation details of your wizard. The example below is from createProfileWizard.js:

var wizardMap = {
    newProfile1_1: { previous: null, next: "newProfile1_2", finish: false },
    newProfile1_2: { previous: "newProfile1_1", next: null, finish: true },
}

Note that there is a row for each page in the wizard, are three columns for each page. The first two columns display the tags for the previous and next pages in the sequence relative to the current tag. The third column is a boolean value which determines whether or not the "finish" button is enabled for the current page. (Finish button can be enabled before the last page if some pages are optional)

Create a row in the wizardMap for each page in your wizard. Head each row up with the tag of the wizard page (see definition of tag above). Include an entry for the previous page tag accessible, the next page tag accessible, and whether or not the finish button is enabled on this page. If there is no previous or next page (i.e. the page is the first or last in a sequence), use null instead of a value. This null value is used by the WizardManager for default button enabling.

You must now write a Startup() function. The Startup() function from createProfileWizard.js is listed below:

// startup procedure
function Startup( startPage, frame_id )
{
  // instantiate the Wizard Manager
  wizardManager                   = new WizardManager( frame_id );
  wizardManager.URL_PagePrefix    = "chrome://profile/content/";
  wizardManager.URL_PagePostfix   = ".xul";
  wizardManager.wizardMap         = wizardMap;

  // set the button handler functions
  wizardManager.SetHandlers( null, null, onFinish, onCancel, null, null );
  // load the start page
  wizardManager.LoadPage( startPage, false );
}

Startup takes two parameters, the tag for the first page to be displayed (see above) and the id/name of the frame that panels will be loaded in (as described above). Startup must then create a WizardManager object, as shown in the example, passing in the frame_id parameter as an argument. You can then set the path for panels with URL_PagePrefix and the extension for panels (URL_PagePostfix) as properties. Then you must set the wizardMap attribute of WizardManager to the wizardMap you created earlier.

Conversely, you can set all of these things in the construction of the WizardManager:

 WizardManager( frame_id, path, extension, wizardMap ) ;

Now you must define which wizard event handlers you wish to write yourself, and which of the WizardManager's you want to use. The WizardManager provides defaults for next, previous, cancel and finish button clicks, its own default button enabling scheme, and its own page-load handler. You can choose to override any or all of these by passing your own function names into the SetHandlers() member function of WizardManager. To use the defaults, simply pass "null" (it is important that you pass null for all handlers you do not intend to use).

The general form of SetHandlers is:

 SetHandlers( next, back, finish, cancel, load, enabling ) ;

This tells the WizardManager which functions you plan on writing yourself, and which it should not use the defaults for.
 wizardManager.SetHandlers( null, null, onFinish, null, null, null );

In the example above, we have chosen to override only the onFinish function, choosing the name "onFinish" for our function. We will define this function in our wizard JS file. Typically, you will only need to override onFinish, because you will want to do something with the data you have collected (!). This is so important that the WizardManager will tell you off if you try to use the default ;-) For the others, you can safely use the defaults, which provide generic page switching and exiting. The Profile Wizard example used here also overrides onCancel, because when cancelled it must stop the event loop of the application, not just close the window.

Finally, a call to the LoadPage() method of WizardManager with two parameters (the start page tag, and "false" -- the false means to build a complete URL based on the path and extension provided, full URL provision is not yet supported but it will come).

Create Your Wizard JS

Now you have to write your own JS. Provide functions for those you have overridden, remember to use the same names as the ones you passed into SetHandlers. Provide any other JavaScript you need.

Create Your Panels

Your panels consist of the panel XUL file (whose name section represents the tag of the panel), and any associated JS and CSS files etc. How you implement this is pretty much left up to you, but you should take note of how data is collected and persisted when the user switches panels.

If you include form elements (e.g. text fields, radio buttons etc) in your panel, the values will be havested automatically when the user changes pages and the data stored in the PageData array, which you can access through script like so:

wizardManager.WSM.PageData

If you have non-form widgets like trees as well as form widgets, you may wish to write some JS in your panel that populates hidden form fields with appropriate values from tree selections, etc. Future versions of this library may automatically harvest data from tree elements, etc.

If you would rather have complete control over how your data is saved and restored, you can define functions in your panel called "GetFields" and "SetFields" (do not use these function names for anything else, or else automatic form field retrieval will break!). If you define these functions, the WizardManager will call these instead of automatically grabbing data. You must write these functions yourself.

GetFields is a function that returns the data to be saved in the PageData array. GetFields must return an array of arrays, each array in the array is a name/value pair for each bit of data you wish to persist. e.g., here's a typical return statement from a GetFields function:

  return [["ProfileName",profName],["ProfileDir",profDirContent]];

This array is stored in PageData by the WizardManager, which calls the function.

SetFields is called once for each piece of data in PageData. Yes I know this is inefficient, but this is an advanced feature so deal, or fix my buggy code ;) You will have to detect which data you want (by looking at the name of the name/value pair given SetFields). Since you normally only have a few controls in each page, this isn't much of a problem. The SetFields function then uses this data to populate the appropriate widget in the correct way. You define this yourself. Remember, this function is called once for each element in PageData, so you may like to use a switch statement or something if you have many controls, and dump everything else down out the default: at the bottom.

Examples of these two functions in use are in newProfile1_2.js in the profile content directory.

Now, all things being well and good in the world, your wizard should now work. If it does not, either you've screwed up, or you've found a bug in our wonderful library. Please email rgoodger@ihug.co.nz for help :)

Reference