Making progress

The progress bar is a series of buttons that allows the user to determine how far they are along the questionnaire process and skip back to an earlier step. Each button needs to be aware of the user's position in the questionnaire in order to determine whether it should be enabled or disabled. The "start" and "end" buttons are fixed, wherein they are available on every questionnaire, but the numbered step buttons need to be automatically generated and bound to the steps for currently loaded questionnaire. Let's take a look at the code:

// packages/wizard/src/view/Progress.js
Ext.define('Wizard.view.wizard.Progress', {
    extend: 'Ext.Container',
    xtype: 'wizard-progress',
    
    config: {
        steps: null
    },
    defaultBindProperty: 'steps',
    defaultType: 'button',
    baseCls: 'wizard-progress',
    layout: {
        type: 'hbox',
        pack: 'center'
    },
    
    applySteps: function(steps) {
        
        var lineHtml = '<div class="wizard-progress-bar"></div>',
            stepArr = steps.getData().items,
            items = this.buildProgressIcons(stepArr),
            container;

        this.removeAll();
        
        items.unshift({ text: 'Start', stepIndex: 0 });
        items.push({ 
            text: 'End', bind: {
                disabled: '{isNotLastStep}'
            }
        });

        container = this.add({
            xtype: 'container', cls: 'wizard-progress-inner',
            defaultType: 'button', items: items
        });

        container.getEl().insertHtml('afterBegin',  lineHtml);

        return steps;
    },


    buildProgressIcons: function(steps) {
        return Ext.Array.map(steps, function(step, i){
            return {
                text: i + 1, stepIndex: i + 1,
                bind: { disabled: '{!isEnabled}' },
                viewModel: {
                    formulas: {
                        isEnabled: function(get) {
                            return get('currentPosition') > i;
                        }
                    }
                }
            };
        });
    }
});

There's a surprising amount going on in this component. Remember that in the wizard panel, I set the bind config on this progress component to the questionnaire steps, and in the preceding code, you can see the custom steps configuration option—as long with the defaultBindProperty—that enables this.

Tip

Remember that defaultBindProperty allows you to avoid explicitly setting the property to bind to, and Ext JS will automatically use the default.

I'll skip over the layout, cls, and defaultType options and move on to the way I implemented applySteps. It builds an array of the following buttons:

  • The start button, which is always enabled
  • The number buttons for each step, which are only enabled if the user has advanced to that step
  • The end button, which is only enabled when the user is on the questionnaire conclusion

The end button's disabled state is deferred to a binding on the parent view model. For the individual step icons, a simple view model with the isEnabled formula is used to toggle the disabled state based on the current active step pane.

When I was researching Chapter 2, MVC and MVVM, I found a little snippet describing MVC in Smalltalk and the way that individual UI components would have their own controller right down to text fields and the like. While it's rare that we'd go that far in our Ext JS applications, the use of a small, one formula view model for the step icons reminded me of this concept.

My only gripe as I was writing it was that while the functionality was great, the syntax to just bind one property like this felt a little verbose. On the other hand, introducing another shorthand syntax would mean a new feature for Ext JS developers to learn; I think we have enough already!

A final point regarding this component is that I append lineHtml to the container and configure a couple of styling hooks. This allows for a thin line connecting the progress buttons; it's a minor visual element, but has a nice effect.

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

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