The final, and in my opinion most important, part of the puzzle is the top-level view model. This is the one that the wizard panel uses directly and is available to all child components of the panel thanks to the view model inheritance. Here's the code:
// packages/wizard/src/view/WizardModel.js Ext.define('Wizard.view.wizard.WizardModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.wizard', data: { currentPosition: 0 }, formulas: { currentStep: function(get) { var pos = get('currentPosition') – 1; return get('questionnaire').steps().getAt(pos); }, stepCount: function(get) { return get('questionnaire').steps().count(); }, isIntroduction: function(get) { return get('currentPosition') === 0; }, isNotLastStep: function(get) { return get('currentPosition') < get('stepCount') + 1; }, isNextEnabled: function(get) { // when current step is valid var stiv = get('currentStep') ? get('currentStep.valid') : true; // when not last step var last = get('isNotLastStep'), return stiv && last; } } });
Note that currentPosition
is initialized at 0
, which represents the questionnaire introduction page in the UI and the first card in the wizard panel.
This one value is probably the most important in the whole application because it not only drives what is displayed on the UI through its binding to activeItem
on the wizard panel, but also the state of the navigation and progress buttons. This is both direct—the progress component inherits and consumes currentPosition
to set the disabled state of its buttons—and indirect, as in the way isNextEnabled
uses it to get the currentStep
model's validity and in turn is bound to the "next" navigation button.
Building up a couple of dependent formulae and allowing them to cascade down to child components gives a clear picture of how data flows from a single source (questionnaire
and currentPosition
on the wizard view model).