I'm using the latest Meteor and Iron Router. Imagine have a 'customComputer' collection. Computers have three different states: 'ordering', 'building', 'shipped'.
Right now, I'm using three different routes for this, each with a different template
/o/_id
/b/_id
/s/_id
A computer can't be in 2 states at once, so I'd like to have one route. How do I wrangle the templates?
/c/_id
The best I can come up with is to make a "main" template that links to the others. Is this a best practice?
{{#if isOrder}}{{>orderTemplate}}{{/if}}
{{#if isBuilding}}{{>buildingTemplate}}{{/if}}
{{#if isShipped}}{{>shippedTemplate}}{{/if}}
Or dynamic templates
Here's the route:
Router.route('order', {
path: '/o/:b/:computerId',
onAfterAction: function() {
if (this.title) document.title = this.title;
},
data: function() {
if(!this.ready()) return;
var o = Computer.findOne(this.params.computerId);
if(!o) throw new Meteor.Error('Computer not found');
return o;
},
waitOn: function() {
if (!Meteor.userId()) return this.next(); // Don't subscribe if not logged in.
return [
Meteor.subscribe('computerById', this.params.computerId),
Meteor.subscribe('myProfile'),
];
},
});
Is there a better way?
I'd do your main template idea, or the dynamic template.
dynamic template tends to be better when you have quite a few options that can be dynamically configured.
But the main template I think ends up being more obvious when you only have a couple of choices.
Either way can be converted easily to the other if you think you need the other option.
Related
whats the bestway to handle subscription based data. For example, you have a game where you have to create the character first before you can do any other things. Currently I thougth I can try to handle it with a onBeforeAction filter. So I have a global controller for every route that needs a the character.
DefaultController = LayoutController.extend({
onBeforeAction : function() {
var currentCharacter = Character.getCurrent.call({});
if(currentCharacter === undefined) {
this.render('CharacterCreate');
} else {
this.next();
}
},
waitOn() {
this.subscribe('characters.owned');
}
});
You have a route like this:
Router.route('/game', { controller: 'DefaultController' });
The problem is until the the collection is loaded the game template will shown. Is there a better approach like this? And another problem when a route needs a character it throws an exception until the subscription is loaded.
Just use a loading hook while the subscriptions are being loaded.
loading(){
this.render('myLoadingTemplate');
}
The loading hook is run automatically while waiting for subscriptions to be ready.
You might find my post on building a clean router.js file useful.
I have an application which makes heavy use of features, separated by specific users, which have different roles.
The problem is that I want to restrict access to some templates, if, for instance, the user is not an Admin.
Currently, I have this:
Router.route('createUser', {
path: '/admin/users/',
onBeforeAction: function() {
if(!isAdmin()) {
Router.go('/');
}
this.next();
}
});
But, specifying that if(isAdmin()) call to every other route is a pain. I want to know if there is any other easy and less error prone way to do it.
Maybe some regex magic would do, but I don't seem to find any examples of use.
First i will recommend you to read this meteor:common-mistakes on the profile editing part
So i will recommend you to better use the alanningroles-meteor package.
Is super easy to use, here is a Online DEMO and the Source Code if you have doubts.
On the router level you can create an onBefore hooks like this.
isAdmin = function(){
var currentUser = Meteor.user()
isUserSuperAdmin = Roles.userIsInRole(currentUser,'Super-Admin'); //using alaning roles.
if(isUserSuperAdmin){ //or use isAdmin();
this.next();
}else{
this.render('accessDenied')
}
}
Router.onBeforeAction('accessDenied', {only: ['admin','otherAdminRoute',etc]});
You can have an onBeforeAction hook combined with only for all routes like so:
var isAdmin = function() {
// Whatever logic you have for checking admin
if (!admin) {
Router.go("/");
}
this.next();
}
Router.onBeforeAction(isAdmin, {
only: ["admin/users"] // Specify other admin templates here
});
Most of the time, my search returns so fast that it's not worth flashing the loading template to the user...(in fact, it's distracting, as people are fine with a blank screen if the results are coming a split second later)...Is there any way to prevent the loading template from showing if the waitOn is only waiting for a short amount of time?
Here's my configuration
Router.route('/search', {
waitOn: function () {
return searchSubsManager.subscribe("search", Session.get('searchString'));
},
action: function () {
this.render('searchResults');
}
});
I saw that with this package:
https://github.com/Multiply/iron-router-progress
you can control whether it shows up for fast routes, but I don't need all that functionality, nor do I want the progress bar it provides... I'm just wondering if the basic iron router/ waitOn functionality can provide this ability.
There is not configuration to use on the waitOn function, but Why don't you create another layout template, and use it to show that fast routes?
<template name="noLoading">
{{> yield}}
</template>
Router.map(function () {
this.route('fastRoutes', {
path: '/someRoutes',
template: 'myHomeTemplate',
layoutTemplate: 'noLoading',
});
});
Update
or use the sacha:spin package and change the class name depending on the duration of the query.
if(queryDuration){
Meteor.Spinner.options = {
className: 'none'
}
}else{
Meteor.Spinner.options = {
className: 'spinner'
}
}
Yesterday I updated meteor and my meteorite packages to their latest versions. Today, iron router is not behaving. When I navigate to a parameterized route, the parameter is not loaded. What gives? I have looked at the documentation for iron-router, and it still specifies the same scheme I was using before.
This is the routes file I have created
Router.map(function() {
this.route('home', {
path: '/'
});
this.route('list', {
path: '/:_id',
waitOn: function() {
return Meteor.subscribe('lists')
},
data: function() {
var list = Lists.findOne({
_id: this.params._id
});
Session.set('listId', list._id);
return list;
}
});
});
When I load a page to http://localhost/1234 the path in iron router is correctly set to /1234 but it does not recognize the last bit as a parameter.
I am afraid that what is empty is not your this.params object but rather the list document, at least for the first time the route is being executed. This is caused, of course, by the latency related to fetching server data.
You may be thinking that it shouldn't happen because you have used the waitOn hook. But for this to work you would also need to do two other things:
Router.onBeforeAction('loading');
and define the loading template:
Router.configure({
loadingTemplate: 'someTemplateName'
});
so please update if you haven't done it already.
Every example that I have seen with iron-router specifies the template name with a string. Is it possible to do this with a variable? Suppose you have several routes that all use the same dynamic path, and the same data function, but they all need different templates. Is there a way to do this without specifying a different route for every template (which would also mean changing the path I use)?
You can programmatically specify things like the template and the layout with a custom action function. The example below demonstrates showing a particular template if the required document is found based on an id in the route. You can use the same semantics for both routes and controllers.
var postsController = RouteController.extend({
waitOn: function() {
return Meteor.subscribe('post', this.params._id);
},
action: function() {
if (Posts.findOne(this.params._id)) {
this.layout('postsLayout');
this.render('posts');
} else {
this.render('notFound');
}
}
});
Once 0.8.2 is released it should be trivial to do with UI.dynamic even without Iron-Router: https://github.com/meteor/meteor/issues/2123.