I started with MartinElvar's excellent answer, but wound up in a different place since I needed to validate a form on each page of the wizard. By making each page of the wizard it's own component, you can easily constrain the validation of each page.
Start with a list of steps on your controller:
// app/controllers/wizard.js
export default Ember.Controller.extend({
steps: ['stepOne', 'stepTwo', 'stepThree'],
currentStep: undefined
});
Then make sure that whenever your controller is entered, bounce the user to the first step:
// app/routes/wizard.js
export default Ember.Route.extend({
setupController (controller, model) {
controller.set('currentStep', controller.get('steps').get('firstObject');
this._super(controller, model);
}
});
Now you can go back to the controller and add some more generic next/back/cancel steps:
// app/controller/wizard.js
export default Ember.Controller.extend({
steps: ['step-one', 'step-two', 'step-three'],
currentStep: undefined,
actions: {
next () {
let steps = this.get('steps'),
index = steps.indexOf(this.get('currentStep'));
this.set('currentStep', steps[index + 1]);
},
back () {
let steps = this.get('steps'),
index = steps.indexOf(this.get('currentStep'));
this.set('currentStep', steps.get(index - 1));
},
cancel () {
this.transitionToRoute('somewhere-else');
},
finish () {
this.transitionToRoute('wizard-finished');
}
}
});
Now define a component for page of your wizard. The trick here is to define each component with the same name as each step listed in the controller. (This allows us to use a component helper later on.) This part is what allows you to perform form validation on each page of the wizard. For example, using ember-cli-simple-validation:
// app/components/step-one.js
import {ValidationMixin, validate} from 'ember-cli-simple-validation/mixins/validate';
export default Ember.Component.extend(ValidationMixin, {
...
thingValidation: validate('model.thing'),
actions: {
next () {
this.set('submitted', true);
if (this.get('valid')) {
this.sendAction('next');
}
},
cancel () {
this.sendAction('cancel');
}
}
});
And finally, the route's template becomes straight forward:
// app/templates/wizard.hbs
{{component currentStep model=model
next="next" back="back" cancel="cancel" finish="finish"}}
Each component gets a reference to the controller's model and adds the required data at step. This approach has turned out to be pretty flexible for me: it allows you to do whatever crazy things are necessary at each stage of the wizard (such as interacting with a piece of hardware and waiting for it to respond).