I would like to devote a single route of my EmberJS app to being a multi-step form. This is the only time I want my URL to remain unchanged, so location: 'none'
is not an option (as far as I can tell). I have controllers at other routes that are tightly integrated with the URL as they should be.
But at this single, unchanging URL I would like to accomplish the following:
.save()
-ed on submit.The way handlebars works is really throwing me for a loop on this.
I have been pouring over the documentation, but can't really find an example. I have a feeling that it is a case where I just don't know what I don't know yet. So if someone could point me in the right direction, hopefully that's all I'd need.
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).
You could achieve this with some actions, and some values that defines the state of the form.
Your controller could have some states properties like the following.
// Step one is default.
stepOne: true,
stepTwo: false,
stepThree: false,
How you want to transition from step to step, is a matter of use case, but you would end of changing the step properties, like so.
actions: {
toStepTwo: function() {
this.set('stepOne', false)
this.set('stepOne', true)
},
// But you could put this with some other functionality, say when the user answer a question.
answerQuestion: function() {
// Run some question code.
// Go to next step.
this.set('stepOne', false)
this.set('stepOne', true)
},
}
In your template you can just encapsulate your content using the if helper.
{{#if stepOne}}
Step one
{{/if}
{{#if stepTwo}}
This is step two
{{/if}}
So the reason for create 3 step properties here, instead of
currentStep: 1,
Is for the sake of handlebars, currently you can't match a current step like so.
{{#if currentStep == 1}}
Well unless you create a handlebars block helper.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With