The wizard component

I felt pretty good about the data layer I'd built. It was frustrating to have spent time trying to work around this limitation to validators that I found, but the final result works well. It was time to move on to the user interface, starting with the main container for the questionnaire wizard:

// packages/wizard/src/view/Wizard.js
Ext.define('Wizard.view.wizard.Wizard', {
    extend: 'Ext.Panel',
    xtype: 'wizard',
    requires: [
        'Wizard.model.Questionnaire',
        'Wizard.model.Step',
        'Wizard.model.Question'
    ],
    ui: 'wizard',
    bodyCls: 'wizard-body',

    viewModel: 'wizard',
    controller: 'wizard',
    
    layout: 'card',
    
    config: {
        questionnaire: null
    },
    bind: {
        questionnaire: '{questionnaire}',
        activeItem: '{currentPosition}',
        title: '{questionnaire.title}'
    },

    applyQuestionnaire: function(questionnaire) {
        if(!questionnaire) {
            return;
        }

        var intro = questionnaire.get('introduction'),
            conclusion = questionnaire.get('conclusion'),

        this.add({ html: intro });

        questionnaire.steps().each(this.addStepPane, this);

        this.add({ html: conclusion });

        return questionnaire;
    },

    setActiveItem: function() {   
        if(this.items.length > 0) {
            this.callParent(arguments);
        }
    },

    addStepPane: function(step, i) {
        this.add({
            xtype: 'wizard-step',
            viewModel: {
                data: { step: step }
            },
            bind: { step: '{step}' }
        });
    },

    load: function(id) {
        this.getViewModel().setLinks({
            questionnaire: {
                type: 'Wizard.model.Questionnaire',
                id: 1
            }
        });
    },

    dockedItems: [
        { xtype: 'wizard-navigation', dock: 'bottom' },
        {
            xtype: 'wizard-progress', dock: 'bottom',
            bind: '{questionnaire.steps}'
        }
    ]
});

I'll break down each of the important parts of this code and try and explain the design decisions behind them.

The ui and bodyCls options are set as a way of hooking into this component via theming and CSS later. In particular, the ui option is a great way of reusing parts of the Ext JS theming system with your own components. We'll revisit this towards the end of the chapter.

After configuring the viewModel, controller, and layout options, I use a tactic that we've seen before, which is to create a new custom configuration option that will be used to bind to. I created a questionnaire config for me to bind a view model value to and you can see that bound value is also called questionnaire.

The final piece of the configuration puzzle is to bind the title of the questionnaire to the title of the wizard panel itself.

Do it yourself

One of my favorite things about creating custom configuration options is that you get an extra hook in the form of an applyOptionName method. It's created automatically for each config option and is called by Ext JS before setting the value. It lets us customize or validate the configuration option and if we've bound a view model value to it, it lets us perform actions when the bound value updates.

I used this with applyQuestionnaire, which is used to build the items for the wizard panel when the questionnaire is bound. It performs the following three actions:

  1. Adds a container for the questionnaire introduction.
  2. Adds a container for the questionnaire conclusion.
  3. Adds a wizard-step component for each step in the questionnaire using the addStepPane method.

Within addStepPane, the new wizard-step component is supplied with a view model that contains the Step model itself and immediately binds this to a step config option. It will be simpler to just pass in the Step model as a configuration option rather than using view models, but this would mean that we can't use this step in further binding formulas and it will be more awkward to react to changes in Step, such as validation.

The second use of apply is via applyActiveItem, which will be triggered every time the currentPosition value on the view model changes. It's used to update the panel's currentPosition to switch from card to card as the user progresses through questionnaire, but I added in a check to ensure the wizard's items have been initialized before doing this. Without this check, setting currentPosition can raise an error if currentPosition is changed before the items have been set up.

Wizardly tools

The last piece of configuration for the main wizard panel is to create the navigation buttons and the progress indicator. I added these as dockedItems with the dock set at the "bottom" in order to have them in the footer of the panel. The progress bar is bound to the questionnaire's steps from the view model in order to build its step icons.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset