how to run a Router.route() in a reactive way - meteor

if I understand this correctly I can use Session.get() to re-run a Router.route(), however it's not working for me. this one of my route:
Router.route("dashboard", function () {
"use strict";
var env = Session.get("ENV");
console.log(env, "router-unisight");
if (env && (env.indexOf("all") === 0 || env.indexOf("unisight") !== -1)) {
this.subscribe("dashboards");
this.subscribe("uTree").wait();
if (this.ready()) {
this.render("dashboard");
this.render("toolbar", {to: "subheader"});
} else {
this.render("loading");
}
} else {
this.render("notfound");
}
});
I have a console.log() that displays only once and I know for a fact that after Session.setDefault("ENV", null); is called this variable is changes one more time
so the question is how can I re-run a route after a Session variable changes?

The route needs to be used by a client before you see the console message. Iron router typically provide client side routes so you won't see the console.log on server side. here is a raw app that shows this working:
http://meteorpad.com/pad/xCG9Epeo3pTYZbLJF/Leaderboard

Related

user object not loaded after logging out and quickly logging back in in Meteor

I am facing a strange behaviour, and can't debug it properly. I need your help.
If I log out of Meteor, everything seems fine.
If I wait for around 2 seconds and log back in again, everything is still very fine. But if I logout and quickly login again, right after the login process, the Meteor.user() object is set to null, which leads my router to redirect the user back to the login page.
Any idea why this is happening, and how could I prevent it?
I have spent 2h trying several things without success. Any suggestion is most welcome.
EDIT
This is my global onBeforeAction function :
Router.onBeforeAction(function() {
// Ensures the user is logged in
if (!Meteor.userId()) {
please_login();
}
// Email address not verified for 24h? Please verify it!
else {
var self = this;
// Waits for the user object to be passed over DDP
function wait_for_user_data() {
if (Meteor.user() && Meteor.user().emails && Meteor.user().profile) {
var user = Meteor.user();
var now = new Date().getTime();
var ca = user.createdAt.getTime();// Created At
var cs = (now - ca) / (24 * 60 * 60 * 1000);// Created Since (in days)
var urls = ["email_verification_required", "email_verification"];
if (cs > 1 &&
!user.emails[0].verified &&
urls.indexOf(self.url.split("/")[1]) == -1) {
Router.go("email_verification_required");
}
else {
self.next();
}
}
else {
setTimeout(wait_for_user_data, 500);
}
}
wait_for_user_data();
}
},
{except: ['home', 'login', 'register', 'password_recovery', "email_verification", "profile"]})
What actually happens is the following :
When I login right after having logged out, self.next() is called, but the current user properties (Meteor.user().emails and Meteor.user().profile) aren't loaded yet for some reason. They are undefined. As you can see, I tried to work around this by waiting until they are defined, but then I receive the following error message :
Route dispatch never rendered. Did you forget to call this.next()
in an onBeforeAction?
This seems to cause Meteor to set Meteor.user() to null, and so my user gets redirected to the login page...
EDIT BIS
This is how I am handling the publish/subscribe of the users data. I have 2 different pub/sub set, one for all users, and the other one for the logged in user only.
Meteor.publish('users', function() {
var args = {};
var fields = {
'_id': 1,
'createdAt': 1,
'profile.firstname' : 1,
'profile.lastname' : 1,
'profile.lang' : 1,
};
// Only admins can access those sensible information
if (this.userId && Roles.userIsInRole(this.userId, 'admin')) {
fields["emails"] = 1;
fields["profile.celular"] = 1;
args : {$ne : {_id: this.userId}};
}
return Meteor.users.find(args, {fields: fields});
});
Meteor.publish('user', function() {
return Meteor.users.find({_id: this.userId});
});
This is how I subscribe to those two publications within my router's configurations :
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() {
return [Meteor.subscribe('user'), Meteor.subscribe('users')];
},
});
Without a code example (and knowing which router you're using) I'm going to take a guess that you're router code looks something like :
if (!Meteor.user()) {
//... redirect or change tempalte etc
}
There is another value you can check,
if (!Meteor.user() && !Meteor.loggingIn()) {
//... redirect or change tempalte etc
}
you can use various combinations of these to handle the login state, such as having a logging in loading view etc
With more information
The redirect is being called in a callback, currently for the login with password function, having a look at the docs it should be called on the onLogin callback linked as this ensures that login was successful, though doing the redirect here, may not provide the same range of control as doing this in a reactive context such as a before hook on a controller, or an autorun function created in the login template onCreated callback.
The way I've deal with this in the past is to define an explicit subscription to the user object then waitOn that subscription in the router. Much simpler than writing a polling loop but mostly I did it because I needed specific fields that didn't come over by default. Hopefully this can help you with your problem.
Server:
Meteor.publish('me',function(){
if ( this.userId ){
// return the fields I need
return Meteor.users.findOne({ _id: this.userId },{ fields: { field1: 1, field2: 1, ... }});
}
else this.ready();
});
Router:
waitOn: function(){
return Meteor.subscribe('me');
}

Testing with meteor and jasmine: Iron Router Routing does not work as expected

I am trying to write tests for a meteor app with the velocity/jasmine framework.
My boss wants a UI testing (End-To-End) so I need to write tests for the User Interface.
I now have the problem how I should test the normal Navigation through the app. My Idea to test for example the user registration procedure was something like this:
describe 'Login and Usermanagement System', ->
it 'should say the user is logged out when no user is logged in', ->
# This test Works
expect(Meteor.user()).toBeFalsy()
it 'should show a welcome screen if the user is logged out', ->
currentUrl = Router.current().location.get().href;
routeName = Router.current().route.getName();
# This test Works as the startpage in our app (When you hit /) is always system.welcome as long as you are not logged in.
expect(routeName).toBe("system.welcome")
it 'should show a register screen if the user is logged out and clicked on register', (done) ->
Router.go("/register")
routeName = Router.current().route.getName()
# This test does not work as the Router.go seems to be async.
expect(routeName).toBe("system.register")
my problem is the third test. I need some kind of callback when a route has loaded to do the next stuff. Of yourse I could wait for 2 seconds or so, but this would slow down my tests unnecesarily.
Is there such a thing as Router.go(route, options, callback) or how can I get such a behaviour?
Technology we are using: MeteorJS with Iron Router for Routing, Velocity Test Framework with Jasmine for Testing.
You need to use the helper described here in the documentation Integration Tests With Iron Router
Which states:
Save this helper to tests/jasmine/client/integration/lib/wait_for_router_helper.js or tests/jasmine/client/unit/_wait_for_router_helper.js depending on the mode you want to use:
(function (Meteor, Tracker, Router) {
var isRouterReady = false;
var callbacks = [];
window.waitForRouter = function (callback) {
if (isRouterReady) {
callback();
} else {
callbacks.push(callback);
}
};
Router.onAfterAction(function () {
if (!isRouterReady && this.ready()) {
Tracker.afterFlush(function () {
isRouterReady = true;
callbacks.forEach(function (callback) {
callback();
});
callbacks = []
})
}
});
Router.onRerun(function () {
isRouterReady = false;
this.next();
});
Router.onStop(function () {
isRouterReady = false;
if (this.next) {
this.next();
}
});
})(Meteor, Tracker, Router);
Then you use it in your tests like this:
describe('My Spec', function () {
beforeEach(function (done) {
Router.go('/myPage');
Tracker.afterFlush(done);
});
beforeEach(waitForRouter);
it('should do something', function () {
// Your test
});
});

ironRouter onbeforeaction gives 2 errors, but does what I expect it to do

Users login using FB or twitter:
I'm trying to check for multiple things here as you can see. But for some reason I get 2 errors:
1. Exception in callback of async function: TypeError: Cannot read property 'profile' of undefined
2. Route dispatch never rendered. Did you forget to call this.next() in an onBeforeAction?
The funny thing is, this code IS doing what I expected it to do. Route to completeSignup if profile.firsttime = false and if not logged in go to startPage. But I still get these errors, so I must be doing something wrong.
code:
onBeforeActions = {
loginRequired: function() {
if (!Meteor.userId()) {
Router.go('startPage');
} else {
if (Meteor.userId() && Meteor.user().profile.firsttime) {
Router.go('completeSignup');
}
}
this.next();
}
};
Router.onBeforeAction(onBeforeActions.loginRequired, {
except: ['startPage']
});
Meteor.userId() becomes available as part of the login process prior to the arrivial of the user document on the client. Mixing the two in the if actually doesn't do what you want because, for a brief moment, they won't simultaneously return truthy values.
In order to avoid the error you'll need to add some extra guards. Try something like this in your else clause:
var user = Meteor.user();
if (user && user.profile && user.profile.firsttime) {
Router.go('completeSignup');
}

Meteor - Problems caused by Meteor.user() not defined at initial page load. How can I wait until Meteor.user() is available/defined?

In my Meteor app I have some collections I would like to subscribe to immediately upon login, and other collections I would like to subscribe to when the user visits or revisits the initial home page, but not otherwise.
The first set of collections should always be subscribed to throughout, but the second set should be turned off and on as the user leaves and returns to the initial screen.
I have the following code:
Meteor.startup(function () {
Meteor.subscribe('collection_one', Meteor.user().profile.setting_one);
Meteor.subscribe('collection_two', Meteor.user().profile.setting_two);
});
Router.route('/', {
name: 'home',
path: '/',
template: 'home',
waitOn: function() {
return [
Meteor.subscribe('collection_three', Meteor.user().profile.setting_three),
Meteor.subscribe('collection_four', Meteor.user().profile.setting_four),
]
}
});
My problem is that immediately upon startup and immediately upon going to the home page, Meteor.user() returns undefined. I would like to wait until Meteor.user() is defined, and then take these actions. How can I do this?
Meteor.startup() does not run code as a reactive computation, so even though Meteor.user() is a reactive data source it won't trigger a computation.
The reactivity section of the docs has a list of functions that run code as reactive computations.
You can use Tracker (previously called 'Deps') to create a reactive computation, like this:
Tracker.autorun(function () {
if (Meteor.user()) {
Meteor.subscribe('collection_one', Meteor.user().profile.setting_one);
Meteor.subscribe('collection_two', Meteor.user().profile.setting_two);
}
});
But it looks like you're using Iron Router so you could also set a global waitOn() with Router.configure to solve it, like this:
Router.configure({
layoutTemplate: 'MasterLayout',
loadingTemplate: 'Loading',
notFoundTemplate: 'NotFound',
templateNameConverter: 'upperCamelCase',
routeControllerNameConverter: 'upperCamelCase',
// This method will re-run when ever Meteor.user() changes.
waitOn: function () {
// Making sure setting_one and setting_two are available (which they won't be initially)
var setting_one = Meteor.user() && Meteor.user().profile && Meteor.user().profile.setting_one;
var setting_two = Meteor.user() && Meteor.user().profile && Meteor.user().profile.setting_one;
// Subscribe to the published version of the server side collections
return [
Meteor.subscribe('collection_one', setting_one),
Meteor.subscribe('collection_two', setting_two)
];
}
});
The key is to use Tracker.autorun()
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
let username = ''
Tracker.autorun( function(currentComputation) {
if (Meteor.user()) {
username = Meteor.user().username
if (username) // do something with username
return
}
})
currentComputation is optional

How to properly replace this.stop() with pause() on Iron Router blaze integration

When I upgrade Iron Router to blaze integration branch, I began receiving this warning:
"You called this.stop() inside a hook or your action function but you should use pause() now instead"
Chrome console --> iron-router.js:2104 --> client/route_controller.js:193 from package
The code is on client:
Router.before(mustBeSignedIn, {except: ['userSignin', 'userSignup', 'home']});
var mustBeSignedIn = function () {
if (!Meteor.user()) {
// render the home template
this.redirect('home');
// stop the rest of the before hooks and the action function
this.stop();
return false;
}
return true;
}
I tried replacing this.stop() with: pause(), Router.pause() and this.pause() but still does not work. Also I haven't found pause function on iron-router package.
How do I properly replace this.stop() with pause()?
Thanks
From what I can tell the pause function is the first parameter your before hook is getting called with. Not in the docs anywhere, but that's what I gathered from the code and it seems to work.
Here's what I use:
var subscribeAllPlanItems = function (pause) {
var planId = this.params._id;
this.subscribe('revenues', planId).wait();
this.subscribe('expenses', planId).wait();
};
var waitForSubscriptions = function (pause) {
if (this.ready()) { //all the subs have come in
//NProgress.done();
setPlan(this.params._id);
} else { //all subscriptions aren't yet ready, keep waiting
//NProgress.start();
pause();
}
};
Router.map(function () {
this.route('calendar', {
path: '/calendar/:_id',
template: 'calendar',
before: [
subscribeAllPlanItems,
waitForSubscriptions
],
});
//Other routes omitted
});
var requireLogin = function (pause) {
if (Meteor.loggingIn()) { //still logging in
pause();
}
if (!Meteor.user()) { //not logged in
this.render('signIn');
pause();
} else { //logged in, life is good
console.log("requireLogin: logged in");
}
};
//This enforces login for all pages except the below ones.
Router.before(requireLogin, {
except: ['landing', 'signUp', 'signIn', 'forgotPassword', 'resetPassword']
});
I opened an issue on Github about this. Here's the response I got:
Oops I may have not changed the redirect method yet. Just use Router.go as it will work fine now. I will change over this.redirect sometime next week or a PR is welcome. Controllers are now automatically stopped if you change routes in a hook. You can pause the current run by calling the pause method which is passed as a parameter to your hooks and action functions.

Resources