Wizard Plugin

A Wizard is a dialog that consists of multiple pages and guides a user through a multi-step process. Wizards usually involve getting input from the user and then performing some action based on those inputs.

🚧

Use Wizards Wisely

In the past, especially when they became popular, wizards were used for everything - even things that could otherwise be simple dialog or wouldn't need a UI at all. Therefore a word of caution: Use wizards wisely. Make sure you really need to guide a user through a blocking process. If you do, then wizards can be very powerful.

Creating a Wizard

A basic wizard with a single page and a close button.

var plugin = new Wizard("Your Name", main.consumes, {
    title: "The title of the dialog",
    allowClose: true,
    height: 200
});

plugin.on("draw", function(){
    var firstPage = new WizardPage({ name: "first" }, plugin);
    plugin.startPage = firstPage;
});

plugin.show();

Looks like this.

15701570

🚧

Wizard inherits from Dialog()

The interface from Dialog() is available to you as well. That means that you can tweak the button bar of your wizard and use any of the other APIs that Dialog() offers.

Wizard Properties

The following properties can be passed to the Wizard() constructor.

allowCloseBoolean specifying whether the close button is shown.
classThe css class for the wizard dialog.
heightThe height of the wizard dialog.
widthThe width of the wizard dialog.
resizableWhether the wizard dialog is resizable.
titleThe title of the wizard dialog.
modalWhether the dialog is modal. Defaults to true.

Adding Wizard Pages

A wizard lets a user walk through different wizard pages via next and previous buttons. Optionally the wizard can show the user finish and cancel buttons when appropriate. The wizard api lets you decide the sequence of the pages shown. It keeps a history of the shown pages in order for the previous button to provide a consistent history. Of course even this can be influence via events.

Lets start by creating four pages. It is common practice to create the wizard pages in the draw event of the wizard. This way they are created just in time.

plugin.on("draw", function(){
    var intro = new WizardPage({ name: "intro" }, plugin);
    var input = new WizardPage({ name: "input" }, plugin);
    var action = new WizardPage({ name: "action" }, plugin);
    var last = new WizardPage({ name: "last" }, plugin);
    
    plugin.startPage = intro;
});

Settings the startPage property tells the wizard to show that page (in this case intro) when the wizard is first shown.

Adding your own contents

The easiest way to populate a dialog is by adding custom html and css. The following example loads css from wizard.css and adds a div element to the body of the first page.

plugin.on("draw", function(){
    // Insert css
    ui.insertCss(require("text!./wizard.css"), options.staticPrefix, plugin);

    var intro = new WizardPage({ name: "intro" }, plugin);
    intro.on("draw", function(e){
        // Insert HTML from a file
        ui.insertHtml(e.html, require("text!./intro.html"), intro);
    });

    var input = new WizardPage({ name: "input" }, plugin);
    var action = new WizardPage({ name: "action" }, plugin);
    var last = new WizardPage({ name: "last" }, plugin);
    
    plugin.startPage = intro;
});

In the same way you can populate the other pages with contents as well. Make sure to check out the Ace Widgets and Form as they make for a great combination with the wizard pages.

Managing State during the Page's Life Time.

Similar to the Dialog plugin use the show event to manage state of the components in the page.

This example adds a list widget to a wizard page. Each time the page is shown the contents of the list of recalculated.

var input = new WizardPage({ name: "input" }, plugin);

var list;
input.on("draw", function(e){
    list = new List({ container: e.html }, plugin);
});
input.on("show", function(){
    list.setRoot(calculateItems());
});
15701570

Wizard Buttons

The next and previous buttons are automatically turned on when a page is activated. You can use the next event as described in the next section to alter the visible state of the 4 buttons that the wizard has.

The code in the example below would show all buttons except next.

plugin.showNext = false;
plugin.showPrevious = true;
plugin.showFinish = true;
plugin.showCancel = true;

Which looks like this.

15701570

Wizard Interactions

Use the next, previous and cancel events to breathe life into your wizard. You often want to determine the next page based on choices a user has made for instance. The following sections tell you how to do this.

We'll assume there are four pages; intro, input, action and last

Next Event

The next event is fired when a user clicks on the next button. You determine which page to present next to the user by returning a reference to that page in the event handler.

This is what the next event handler might look like for our wizard. After the intro pag the user goes on to the input page. There the user can either click a checkbox or not. If the user checks the checkbox and clicks next they go to the action page. Otherwise they are shown the last page. After the action page, the last page is shown as well.

plugin.on("next", function(e){
    var page = e.activePage;
    
    // Intro -> Input
    if (page.name == "intro") {
        return input;
    }
    // Input -> Action (if checked) 
    else if (page.name == "input") {
        if (page.container.querySelector(".checkbox").checked) {
            plugin.showPrevious = false; // We don't allow to go back from here
            plugin.showCancel = true; // Allow the user to cancel the process
            return action;
        }
    }
    
    // In all other cases, show the last page
    plugin.showFinish = true;
    plugin.showPrevious = false;
    plugin.showNext = false;
    return last;
});

Previous Event

The previous event is called when a user uses the previous button to navigate to a page.

plugin.on("previous", function(e){
   var page = e.activePage;
   if (page.name == "intro")
       plugin.showFinish = true;
});

Cancel Event

The cancel event is called when a user clicks the cancel button.

plugin.on("cancel", function(e){
   abort();
   plugin.gotoPage(last);
});

Navigating

You can programmatically trigger the actions that correspond to the buttons.

plugin.next()
plugin.previous()
plugin.cancel()
plugin.finish()

In addition you can display any page automatically.

plugin.gotoPage(intro);

The page will be added to the history when using gotoPage().

Fetch a reference to the active page like this.

var page = plugin.activePage;

Full Example

define(function(require, exports, module) {
    main.consumes = ["Wizard", "WizardPage", "ui"];
    main.provides = ["yourplugin"];
    return main;

    function main(options, imports, register) {
        var Wizard = imports.Wizard;
        var WizardPage = imports.WizardPage;
        var ui = imports.ui;
        
        /***** Initialization *****/
        
        var plugin = new Wizard("Your Name", main.consumes, {
            title: "The title of the dialog",
            allowClose: true,
            height: 400
        });
        
        plugin.on("draw", function(){
            // Insert css
            ui.insertCss(require("text!./wizard.css"), options.staticPrefix, plugin);
        
            var intro = new WizardPage({ name: "intro" }, plugin);
            intro.on("draw", function(e){
                // Insert HTML from a file
                ui.insertHtml(e.html, require("text!./intro.html"), intro);
            });
        
            var input = new WizardPage({ name: "input" }, plugin);
            input.on("draw", function(e){
                // Insert HTML from a file
                ui.insertHtml(e.html, require("text!./input.html"), input);
            });
            
            var action = new WizardPage({ name: "action" }, plugin);
            action.on("draw", function(e){
                // Insert HTML from a file
                ui.insertHtml(e.html, require("text!./action.html"), action);
            });
            
            var last = new WizardPage({ name: "last" }, plugin);
            last.on("draw", function(e){
                // Insert HTML from a file
                ui.insertHtml(e.html, require("text!./last.html"), last);
            });
            
            plugin.on("next", function(e){
                var page = e.activePage;
                
                // Intro -> Input
                if (page.name == "intro") {
                    return input;
                }
                // Input -> Action (if checked) 
                else if (page.name == "input") {
                    if (page.container.querySelector(".checkbox").checked) {
                        plugin.showPrevious = false; // We don't allow to go back from here
                        plugin.showCancel = true; // Allow the user to cancel the process
                        start();
                        return action;
                    }
                }
                
                // In all other cases, show the last page
                plugin.showFinish = true;
                plugin.showPrevious = false;
                plugin.showNext = false;
                return last;
            }, plugin);
            
            plugin.on("cancel", function(e){
               abort();
               plugin.gotoPage(last);
            }, plugin);
            
            plugin.startPage = intro;
        });
        
        /***** Functions *****/
        
        function start(){
            
        }
        
        function abort(){
            
        }
        
        /***** Lifecycle *****/
        
        plugin.on("load", function(){
            
        });
        
        plugin.on("unload", function(){
            
        });
        
        /***** Register and define API *****/
        
        /**
         * Your wizard description
         **/
        plugin.freezePublicAPI({
            
        });
        
        register(null, {
            "yourplugin": plugin
        });
    }
});