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.
Related
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
});
I want to subscribe to data on specific pages, so I put subscribe() inside router.js. I am not sure if I should enclose it inside Meteor.isClient() block. Should I? When will I ever do routing for the server-side?
Router.route('/courses/:_id', function () {
if (Meteor.isClient) {
Meteor.subscribe("comments");
}
this.render('CourseDetail', { .. });
});
Instead of putting the if (Meteor.isClient){} check inside of your router.js file, you can simply remove that check and put the file inside of the top-level client folder in your application directory. This way, you do not have to worry about your routes being processed on the server at all. In making that change, you can structure your route definition above in one of two ways:
Router.route('/courses/:id', function() {
this.wait(Meteor.subscribe('comments')); // Either this one
this.subscribe('comments').wait(); // or this one. DO NOT DO BOTH.
if(this.ready()) {
this.render();
} else {
this.render('CourseDetail');
}
});
or:
Router.route('/courses/:id', {
subscriptions: function() {
this.subscribe('comments');
},
action: function() {
this.render('CourseDetail');
}
});
Notice that the first option passes a function as the second parameter to the Router.route() function while the second option passes an object as the second parameter to the Router.route() function. Both options are perfectly valid. For information on the first option, check this out; for information on the second option, check this out.
As for when you would ever do server-side routing, this is usually done if you are setting up an HTTP request/response part of your application for external applications to access your server. Unless this is the case, you would most likely never need to worry about setting up such a thing. In the case of doing this, however, you would define your routes and put them in the top-level server folder in your application directory. for information on server-side routing, check this out.
No need to use the Meteor wrapper. Iron router has its own syntax for saying where you want your route to run.
Here is an example.
Router.route('/item', function () { var req = this.request; var res = this.response; res.end('hello from the server\n'); }, {where: 'server'});
Here is the docs site.
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md
You should define the subscription in the onBeforeAction option of the iron router route.
Router.route('/courses/:_id', function () {
onBeforeAction: function () {
Meteor.subscribe("comments");
},
action: function (){
this.render('CourseDetail', { .. });
}
});
Source
Current common Iron Router pattern is to display a loading template while waiting for data to be available. But I would prefer simply to wait on the previous rendered template/data context until data is available and then trigger rerendering. Data is quickly available, so that short flicker of loading template is worse than a short delay user will experience after a link click.
Does this pattern work for you?
Router.route('/', {
name: 'nameOfTemplate',
data: function() { return CollectionName.find({title: 'nameOfMongoDBQuery'}); },
waitOn: function() { return Meteor.subscribe('nameOfSubscription'); } // waits until resources arrive before rendering page
});
You'll want to specify an explicit action function like so:
action: function() {
if (this.ready()) {
this.render();
}
}
This will just not render anything until the data is around.
I have a /user route set up, which is supposed to render the login template if the current user isn't logged in. The entire router has a waitOn that waits for the currentUser subscription to finish. The problem is that when I go to /user it simply renders the dataNotFound template instead.
Here's the snippets of code that are relevant to this situation. I've been careful to show you them in the order they're defined in my lib/router.js file.
Router.plugin('dataNotFound', {notFoundTemplate: 'notFound'});
Router.onBeforeAction(function () {
console.log(Meteor.userId())
if (!Meteor.userId()) this.render('login');
else this.next();
}, { only: ['user'] });
Router.configure({
waitOn: function () { return Meteor.subscribe('currentUser'); }
});
Router.route('/user', {
name: 'user',
template: 'userView',
data: function () { return Meteor.user(); }
});
That console.log above doesn't even ever fire. It seems to me that since it should be a reactive function that even if initially the dataNotFound is rendered, then soon after that the onBeforeAction should be fired and render the login template, right?
It's very bizarre that your console log doesn't even fire. I have a few ideas, but first want to address the last piece of your question.
The dataNotFound plugin is triggered when the data function fires on your rout. This means it is bypassing your onBeforeAction hook altogether, and not that it isn't getting there.
One thing I can think of that might be worth trying would be wrapping the 'user' route action in a if ( this.ready() ) statement:
edit:
Router.route('user', {
// Your other stuff
action: function() {
if this.ready() {
this.render();
},
The reason I suggest this is just that you are using a waitOn statement, but I'm not 100% sure how that works if you don't have a this.ready() somewhere in your route, because that's what (again, from my reading of the documentation, have not fiddled around with it) tells the router what to wait before executing. Possibly it's not waiting at all right now.
I had a problem with onBeforeAction hook after upgrading from meteor 0.9.1 to 1.
It didnt get fired when it should. Like after I log out, I enter address manually and instead of login page I can see the actual site waiting for data that never comes. onRun hook solved it, but as docs says it gets fired only once.
Router.onRun(function () {
if (!Meteor.userId()) {
this.render('login');
} else {
this.next();
}
}, { only: ['user'] });
Try swapping out Meteor.userId() with Meteor.user() in your if statement. See this link for reference on how to handle checking for the user in a before filter: http://www.manuel-schoebel.com/blog/meteorjs-iron-router-filters-before-and-after-hooks.
I found the issue here.
According to this post, thanks to Steeve Cannon. The problem is on waitOn function.
Probably when you logginOut the subscribe to currentUser will fail, and this will make your app in infinite waiting. onBeforeAction runs after waitOn
You will need to check all variables in you Publish to insure waitOn complete successfully.
Put an if statement inside the waitOn function.
waitOn: function () {
if(Meteor.userId())
return Meteor.subscribe('currentUser');
}
Refer this comment to know why this is happening: https://github.com/iron-meteor/iron-router/issues/1010#issuecomment-72210587
I'm using laika for testing and the meteor-router package for routing. I want to do tests that navigate to some page, fill a form, submit it and check for a success message, but I'm stuck on the navigation part. This was my first attempt:
var assert = require('assert');
suite('Router', function() {
test('navigate', function(done, server, client) {
client.eval(function() {
Meteor.Router.to('test');
var title = $('h1').text();
emit('title', title);
})
.once('title', function(title) {
assert.equal(title, 'Test');
done();
});
});
});
This doesn't work because Meteor.Router.to doesn't have a callback and I don't know how to execute the next line when the new page is loaded.
I tried also with something like this
var page = require('webpage').create();
page.open('http://localhost:3000/test', function () {
...
}
but I got the error Error: Cannot find module 'webpage'
Edit
I'm moving to iron router, so any answer with that also will be helpful.
I had the same problem. I needed to navigate to some page before running my tests. I'm using iron router as well. I figured you can't just execute Router.go('foo') and that's it. You need to wait until the actual routing took place. Fortunately the router exposes a method Router.current() which is a reactive data source that will change as soon as your page is ready. So, in order to navigate to a specific route before running my tests, I firstly run the following code block:
// route to /some/path
client.evalSync(function() {
// react on route change
Deps.autorun(function() {
if (Router.current().path == '/some/path') {
emit('return');
this.stop();
}
});
Router.go('/some/path');
});
Since this is within an evalSync()everything that follows this block will be executed after the routing has finished.
Hope this helps.
Laika now includes a waitForDOM() function you can set up to wait for a specific DOM element to appear, which in this case would be an element in the page you're loading.
client.eval(function() {
Router.go( 'test' );
waitForDOM( 'h1', function() {
var title = $('h1').text();
emit( 'title', title );
});
});
The first parameter is a jQuery selector.