I have a requirement to make what is essentially a dynamic form (wizard) that has multiple steps. Now I want to be able to quickly add new steps to the wizard in the future (or remove them) so I don;t to create separate routes for each step like so:
this.resource('wizard', { path: '/' }, function() {
this.route('step1', { path: '/' });
this.route('step2');
this.route('step3');
this.route('step4');
this.route('step5');
});
I would much prefer to have a dynamic segment that takes in the name of the step and loads the corresponding template of the same name, like so
this.resource('wizard', { path: '/' }, function() {
this.route('step', { path: '/:step' });
});
Is this at all possible or is this just wishful thinking.
I have come up with a solution but I am not sure it is considered the best...
I have defined a route in the router to take in a dynamic segment with the name of the template:
this.resource('wizard', { path: '/wizard' }, function() {
this.route('missing', { path: '/:step' });
});
I have then created a missing route that takes this dynamic segment from the model and uses it to load in the template into the appropriate outlet
export default Ember.Route.extend({
renderTemplate: function(controller, model) {
this.render('wizard/' + model.step, {
controller: controller
});
}
});
I would love to hear some thoughts on this solution.
Related
I am trying to build dynamic routes for an admin section on my site so that "/admin" would work as well as "/admin/users" and "/admin/users/add" and so on. I have tried some different combinations but still struggling with this. Below is what I've tried and in different orderes.
Ideally if I could just specify "/admin" but dynamically reference each new / as an argument that would be best for handling in the code. Example "/admin/1/2/3/4/5" and being able to reference the 1 2 3 etc. I didn't see anything like this in the docs though.
Router.route('/admin', {
name: 'admin',
path: '/admin',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('apage', 'dashboard');
Session.set('asect', null);
this.render();
}
});
// Not Working...
Router.route('/admin/:apage', {
name: 'admin',
path: '/admin/:apage',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('apage', this.params.apage);
Session.set('asect', null);
this.render();
}
});
// Not Working...
Router.route('/admin/:apage/:asect', {
name: 'admin',
path: '/admin/:apage',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('apage', this.params.apage);
Session.set('asect', this.params.asect);
this.render();
}
});
EDIT (Answered)
After some testing it seems calling a template should be (or easiest done) in the this.render() line and the routes should go from most restrictive/detailed to least - which I did try before. The problem seems to be using this.params on the template: line. This solution is not perfect but posting for anyone who may run into a similar problem. As far as further variables in the url like "/admin/1/2/3/4/5" it seems they would need additional routes and can't be fully dynamic as a "/" can not go into the params and the router would look for a route and return notFound unless you can an explicit matching route. There may be a work around that I did not find.
Working code below:
Router.route('adminPage', {
path: '/admin/:asect/:apage',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('asect', this.params.asect);
Session.set('apage', this.params.apage);
this.render('admin_' + this.params.asect + '_' + this.params.apage);
}
});
Router.route('adminSect', {
path: '/admin/:asect',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('asect', this.params.asect);
Session.set('apage', null);
this.render('admin_' + this.params.asect);
}
});
Router.route('admin', {
path: '/admin',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
Session.set('asect', 'dashboard');
Session.set('apage', null);
this.render('admin_dashboard');
}
});
There is a way to have optional parameters in routes (which is what you're looking for unless I'm mistaken). With that in mind, you should be able to manage using one router.
Router.route('admin',{
path: '/admin/:asect?/:apage?',
template: 'admin',
layoutTemplate: 'layout_admin',
action: function() {
var asect = this.params.asect || 'dashboard',
apage = this.params.apage || null,
render = (function(){
if(apage !== null) {
return 'admin_'+ asect +'_'+ apage;
} else {
return 'admin_'+ asect;
}
})();
Session.set('asect', asect);
Session.set('apage', apage);
this.render(render);
}
});
The ? after each parameter in the path designates it as an optional parameter. You should be able to then check if it has been defined or otherwise assign a default value and then structure your view and session accordingly.
Note: You can test in this MeteorPad - just update the URL according to the names of the example templates.
http://meteorpad.com/pad/Ri4Np5xDJXyjiQ4fG
I am trying to create an Ember.js app on a domain that has a ton of different sublevel sites. I'm trying to set my rootURL to '/org/new/' like the docs say so that the root URL will be mydomain.com/org/new/. I'm using the following router code:
App.Router.reopen({
rootURL: '/org/new/'
});
App.Router.map(function() {
this.resource('carouselItems', { path: '/' }, function() {
// child routes
});
});
App.CarouselItemsRoute = Ember.Route.extend({
model: function() {
return this.store.find('carouselItem');
}
});
However, my app keeps trying to load mydomain.com/carouselItems instead. I have a CarouselItemsController that extends ArrayController and a CarouselItemController that extends ObjectController. Both are used when the user navigates to mydomain.com/org/new/ to generate some items in a Bootstrap carousel.
The following route setup is from the Ember.JS docs (http://emberjs.com/guides/routing/defining-your-routes/) and I have to deal with an equivalent problem:
App.Router.map(function() {
this.resource('post', { path: '/post/:post_id' }, function() {
this.route('edit');
this.resource('comments', function() {
this.route('new');
});
});
});
According to the docs and my own experience this results, among others, in the following route:
/post/:post_id/comments -> App.CommentsIndexRoute
However, since I want a post-specific comment route I would have expected
/post/:post_id/comments -> App.PostsCommentsRoute
What exactly is my fallacy and what would I have to change to achieve my goal.
Only route's share their name with their parent resource. If you wanted it to show up as as PostsCommentsRoute it would be more like this (note I pluralized it to match your example, despite the url not being pluralized)
App.Router.map(function() {
this.resource('posts', { path: '/post/:post_id' }, function() {
this.route('comments');
});
});
I have a super controller: ApplicationController which yields to my regions: header/footer. When one of my configured routes attempts to yield to another region on another template, the parent controller's yieldTemplates are overridden.
Example:
ApplicationController = RouteController.extend({
yieldTemplates: {
'footer': { to: 'footer' },
'header': {to: 'header'}
}
});
var SignUpController = ApplicationController.extend({
template: 'signUp'
});
Router.map(function () {
this.route('signup', {
path: '/sign-up',
controller: SignUpController,
template: 'signUp-form',
disableProgress: true,
yieldTemplates: {
'personal-signup': {to: 'signup-detail'}
}
});
});
Any idea why inheritance isn't working in this situation ?
I had a similar issue, read the answer here: https://github.com/EventedMind/iron-router/issues/249#issuecomment-27177558
What's happening is your Router level config is overriding the RouteController prototype. In general, options override prototype properties in iron-router.
A simple solution is to create a global object with the main yields, and then extend that object on each Controller when new yields are necessary:
var mainYieldTemplates = {
'footer': { to: 'footer' },
'header': {to: 'header'}
};
ApplicationController = RouteController.extend({
yieldTemplates: mainYieldTemplates
});
var SignUpController = ApplicationController.extend({
template: 'signUp',
yieldTemplates: _.extend({}, mainYieldTemplates, {
'personal-signup': {to: 'signup-detail'}
}
)
});
Router.map(function () {
this.route('signup', {
path: '/sign-up',
controller: SignUpController,
template: 'signUp-form',
disableProgress: true,
});
});
There is also a minor inconsistency in your code where you declare the "template" property to be "signUp" in the controller, but then in the route itself you set it to "signUp-form". This will overwrite the "template" property on the controller. You can instead create a new controller for each route with all of the route's properties instead of overwriting them.
I got sick of using the solution in the other answer and just ended up monkeypatching RouteController to behave as I'd expect in this case. Works great so far.
(function(){
var orig = RouteController.extend;
RouteController.extend = function(newChild) {
var extendedTemplates = {};
if (!newChild.yieldTemplates) {
newChild.yieldTemplates = {};
}
_.extend(extendedTemplates, this.prototype.yieldTemplates);
_.extend(extendedTemplates, newChild.yieldTemplates);
newChild.yieldTemplates = extendedTemplates;
return orig.apply(this, arguments);
}
})();
Throw that code somewhere before you create any RouteControllers of your own. Any further controllers extended from there will extend onto the yieldTemplates field of the parent.
I have started learning the ember.js framework and I am stuck at how to use the setting of the URL type feature that the framework has.
http://emberjs.com/guides/routing/specifying-the-location-api/
I have this simple application.js
App = Ember.Application.create();
App.Router.reopen({
location: 'history'
});
App.Router.map(function () {
this.route('about');
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return appdata;
}
});
App.IndexRoute = Ember.Route.extend({
setupController: function (controller) {
// Set the IndexController's `title`
controller.set('indextitle', "My Index title");
},
renderTemplate: function () {
this.render({ outlet: 'indexoutlet' });
}
});
App.AboutRoute = Ember.Route.extend({
model: function () {
return appdata;
},
renderTemplate: function () {
this.render({ outlet: 'aboutoutlet' });
}
});
var appdata = { mytext: '', theplaceholder: 'Enter new text', attr:'Yeap!' }
If I don't use the
App.Router.reopen({
location: 'history'
});
the application works fine and it goes to the 'about' route by appending the URL the '~/EmberjsTest.aspx#/about' as it supposed to do.
However because I do not like the hash symbol in the URL of the page, I would prefer if it was removed and to do that the guide says we should put this code:
App.Router.reopen({
location: 'history'
});
But when I do it I get an error in the Chrome console saying:
'Assertion failed: The URL '/EmberjsTest.aspx' did match any routes in your application'
What am I doing wrong?
Thanks in advance
If you want to use the history API then you have two options.
Serve your Ember app from '/' so that Ember can just work with it's "normal" index/root route.
Create a route in your Ember app that can handle '/EmberjsTest.aspx'.
this.route("index", { path: "/EmberjsTest.aspx" });
Note that if you go with option 2 you'll probably have to update all of your routes to include '/EmberjsTest.aspx' in their paths.
this.resource("posts", {path: "/EmberjsTest.aspx/posts" })