I need to create a wizard with multiple steps. Each step will display a form with options, and depending on the user choices, the wizard should go to a certain step, as well as keeping user preferences (choices) stored in some place.
These preferences are not saved in the model, they are only relevant to the step of model creation.
In order to give some context, the goal of this is:
The question is, which would be the best way to accomplish this inside Ember?
Here are my –Ember newbie– thoughts:
outlets
, which should be rendered dynamically according to the current step. This is where I get completely lost. How to do this? Should each step have a different route or not?
Versions being used:
Sounds like you are on the right track.
Create a template for each wizard step.
Yes, that is a good start.
Keep track of the current step. Where? Controller? Route?
Either controller or route would work. Route makes most sense if you want bookmarkable urls for each step and for back/forward to work and is probably the most straightforward solution. Will assume you've chosen route.
Display these templates into outlets, which should be rendered dynamically according to the current step. This is where I get completely lost. How to do this? Should each step have a different route or not?
Since each step will be a route, ember will take care of rendering appropriate template automagically.
Keep track of user answers in the controller.
Once the wizard is finished, load the form template, which will read user preferences stored in controller.
Think of "finished" as just another step. Each step gets it's own controller which is used to record user responses. The last controller uses "needs" to access earlier controllers in order to customize behavior based on responses to the wizard.
I know this an old thread and probably won't help the OP, but stackoverflow leads to more answers than anything else. I just wrote a blog post on this.
Look at this JSBin to see what I've done. I'll summarize here.
Route per step in template:
Router.map(function() {
this.resource('wizard', function(){
this.route('index');
this.route('review');
this.route('complete');
});
});
Custom computed property that changes values when the routes change (in this case when the steps of the wizard changes)
import Ember from 'ember';
var get = Ember.get;
var computed = Ember.computed;
export function routeVal(routeVals, prop){
return computed('currentPath', function(){
var currentRoute = get(this, 'currentPath');
var routeValues = get(this, routeVals);
for (var i = 0; i < routeValues.length; i++) {
if (routeValues[i].route === currentRoute) {
return routeValues[i][prop];
}
}
});
}
A route-value
object:
export default Ember.Object.extend({
route: null
//all other props can be added dynamically
});
A controller mixin for being aware of the current route:
export default Ember.Mixin.create({
needs: ['application'],
currentPath: Ember.computed.alias("controllers.application.currentPath")
});
The resource controller:
import CurrentRouteAware from 'path/to/mixin';
import {routeVal} from 'app_name/utils/macros';
export default Ember.Controller.extend(CurrentRouteAware, {
routeValues: [
RouteVal.create({
route: 'wizard.index',
step: 'Create',
next: 'Review',
nextTransition: 'wizard.review',
prevTransition: 'wizard.index',
showNext: true,
showPrev: false
}),
RouteVal.create({
route: 'wizard.review',
step: 'Review',
next: 'Complete',
prev: 'Create',
nextTransition: 'wizard.complete',
prevTransition: 'wizard.index',
showNext: true,
showPrev: true
}),
RouteVal.create({
route: 'wizard.complete',
step: 'Complete',
next: 'Make Another',
prev: 'Review',
nextTransition: 'wizard.complete',
prevTransition: 'wizard.review',
showNext: false,
showPrev: true
})
],
nextButton: routeVal('routeValues', 'next'),
prevButton: routeVal('routeValues', 'prev'),
nextTransition: routeVal('routeValues', 'nextTransition'),
showButtons: routeVal('routeValues', 'showButtons'),
prevTransition: routeVal('routeValues', 'prevTransition'),
showNext: routeVal('routeValues', 'showNext'),
showPrev: routeVal('routeValues', 'showPrev'),
actions: {
next: function(){
this.transitionToRoute(this.get('nextTransition'));
},
prev: function(){
this.transitionToRoute(this.get('prevTransition'));
}
}
});
Think of the route value object as meaning, "When the route equals routeVal.route, the following properties will have these values" eg "When the currently active route is 'wizard.index' the next transition is to 'wizard.review', the next button text is 'Review', the previous button should be hidden, etc"
And lastly, your resource template:
<div class="wizard" id="wizardIllustration">
{{form-wizard steps=routeValues currentPath=currentPath}}
<div class="actions">
{{#if showPrev}}
<button type="button" {{action 'prev'}}class="btn btn-default btn-prev"><span class="glyphicon glyphicon-arrow-left"></span>{{prevButton}}</button>
{{/if}}
{{#if showNext}}
<button {{action 'next'}}type="button" class="btn btn-default btn-next" >{{nextButton}}<span class="glyphicon glyphicon-arrow-right"></span></button>
{{/if}}
</div>
<div class="step-content">
{{outlet}}
</div>
</div>
You can look at the jsbin for what the form-wizard component was (just a wrapping around the css for Fuelux wizards that keeps the active class on the correct step based on the route). The body of the wizard is the template for each of the subroutes. The next/prev button's text change depending on the route, as do their transitions (since the transition depends on the current state of the wizard). It's more or less a FSM
Answering an old question in case someone needs some answers.
You can use something like this Multi-step form (or multiple "pages") in one route using Ember.js
this utilizes 1 route and each step/page is shown or hidden using {{#if}} To answer Nino's question, when I implemented this I had an object in my controller keeping track of all the values of the form fields and updating it as I click on next. When you get to the last page where you submit, you can simply plug this object into your controller's submit function like this
submitSurvey: function() {
var survey = this.store.createRecord('survey', this.get('surveyObj'));
// surveyObj contains all the form field's values that you've been
// keeping track of everytime you go from one step to another
survey.save();
this.transitionToRoute('somewhere'); // go somewhere after submitting the form
this.set('firstPage', true); // make first page the page to show
this.set('lastPage', false);
this.init();
// constructor's init contains code that empties all the form fields alternatively
// you can create a separate function instead of using init
}
Good for simple implementation of a multi-page form. Problem I have with this is I have jQuery UI code that hooks into the view's didInsertElement and it works just that when I go from one step to another and come back (each page has 'next' and 'prev' buttons), I find that whatever the code that was ran at didInsertElement becomes undone. It's like the chunk of html code wasn't just hidden and re-shown. It was reloaded and thus all effects are gone.
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