Before a user navigates away from my create post route I would like to ask the user for confirmation. If they say no then stop the new route from running. Currently everything is working expect the new route is always rendering; I can't seem to stop the router.
var isUnsavedCreatePost = function() {
bootbox.confirm("Are you sure?", function(result) {
if (result == true) {
this.next();
} else {
}
});
};
Router.onStop(isUnsavedCreatePost, {only: ['createPost']});
Related
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');
}
I created a collection for adminuser's enter the system.I mean I dont want to use account packet for admin side but I dont know How to make Route setting after admin to be login.I made something but it doesnt work correct,
login.html
Template.login.events({
'click #entre': function (e, template) {
var Username = template.$('#username').val();
var Password = template.$('#password').val();
var getinfo= admin.findOne({});
if (Username == " " || Password == "") {
swal("Error", "All fields must be Completed.", "error");
} else if (getinfo.username== Username && getinfo.password== Password) {
swal("Success", "Welcome admin.", "success");
Session.set("hi", true);
} else {
swal("error", "Login Informations wrong.", "error");
}
}
});
router.js
Router.onBeforeAction(function () {
if (!Session.get("hi")) {
this.render('login');
} else {
this.render('dashboard');
}
});
Router.route('userList', function () {
this.render('userList');
});
Router.route('addnewuser', function () {
this.render('addnewuser');
});
Note:I want to make that when admin to be login,it can reach to all pages userlist,addnewuser etc.
If I understood it properly you want to give admin rights to certain routes of yours. There are several ways to achieve this using iron:router, either by building a Controller or using Filters. I would create an Iron Router Controller to tackle that and attach it to any route that needs that kind of checks. Both Controllers and Filters are actually reusable bits of code which is what we are looking for.
Make sure you add alanning:roles to your packages list (if you haven't already) and add some admin roles to at least one of your Meteor.users() like it's shown here
Building the actual Controllers is easy.
lib/router.js
AdminController = RouteController.extend({
onBeforeAction: function () {
var loggedInUser = Meteor.userId();
if (!!loggedInUser) {
if (!Roles.userIsInRole(loggedInUser, 'admin')) {
// Basic redirect to the homepage
Router.go('homepage');
this.stop();
}
} else {
// Log them in when they are not
Router.go('login');
this.stop();
}
this.next();
}
});
Router.route('/admin', {
name: 'admin',
controller: AdminController,
waitOn: function () {
// return subscriptions here
}
});
Continue to add that to any other routes you like.
I have 2 roles, admins and users. I have a home route '/' that is just a sign in page. When admin users sign in they must go to one route ('adminPortal') and when user users log in they must go to the 'userPortal' route. On signing out both roles should route back to '/'.
Before I had an admin role, I was routing on sign in like so:
Router.onBeforeAction(function() {
this.render('loading');
if (! Meteor.userId()) {
this.render('Home');
} else {
this.next();
}
});
which worked fine (actually it was breaking my waitOn: render loading template stuff which I just discovered but more on that later). I then added roles like this (from a Stack Overflow answer I can't find right now):
server/
insertUsers=function(){
var adminId=Accounts.createUser({
username:"admin",
password:"password"
});
Roles.addUsersToRoles(adminId,"admin");
var userIdd = Accounts.createUser({
username:"user",
password:"password"
});
Roles.addUsersToRoles(userIdd,"user");
};
and
Meteor.startup(function () {
// always start from scratch (you will want to comment this line at some point !)
Meteor.users.remove({});
if(Meteor.users.find().count()===0){
insertUsers();
}
})
and
Meteor.publish("user", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'roles': 1}});
} else {
this.ready();
}
});
And I tried to route to the user/admin portals like this:
router.js
Router.route('/', {
before: function() {
if (! Meteor.userId()) { // I acutally added this check that the user is not logged in after the infinite loading problem but I thought the question was getting too long so I just left it in rather
this.render('Home')
} else {
if (Roles.userIsInRole(Meteor.user(), 'admin')) {
Router.go('/admin')
} else {
Router.go('/user')
}
}
},
waitOn: function () {
return Meteor.subscribe('user');
}
});
Now this very almost works! If I log is as either user I go to the right portal. However, when I sign out, my onBeforeAction (i.e. first code block in this question) only renders the Home template and does not actually change the URL to '/' (i.e. the URL remains either '/user' or '/admin'). Now when I try log in a second time, it will always take me to the route that I was taken to on the first log in unless I manually change the browser URL to '/'. So I thought I'd just replace the this.render('Home') with a Router.go('/'); but that seems to have created some sort of infinite loop where the Home template never renders (it did incidentally now for the first time correctly render my loading template though).
So thanks for reading all that! What's the right way to do this?
Try adding Router.go('/'); in your logout button event, along with Meteor.logout();
Example:
Template.loginButtons.events({
'click #login-buttons-logout' : function (event, template) {
Meteor.logout(function(err) {
Router.go('/');
});
}
});
I had the same issue as you, and that was the simplest way I've found to return to home page after logout.
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
});
});
I've got the Meteor Roles package and I'm trying to define an admin route:
var requireLogin = function() {
if (! Meteor.user()) {
debugger // #1
if (Meteor.loggingIn()) {
this.render(this.loadingTemplate);
} else {
console.log("no user");
this.render('AdminLogin');
}
} else {
this.next();
}
};
Router.onBeforeAction(requireLogin, {only: ['AdminMain']});
Router.route('/admin', {
name: 'AdminMain',
layoutTemplate: 'AdminLayout',
waitOn: function(){
debugger // #2
return [
Meteor.subscribe("appointments")
]
}
});
I've got this in server/publications:
Meteor.publish('appointments', function() {
if (Roles.userIsInRole(this.userId, ['assistant','admin'])) {
return Appointments.find();
} else {
console.log("no user");
return [];
}
});
The first debugger that gets set off is debugger #2 in the waitOn. Why? I have an OnBeforeAction specified precisely for that route. According to the Iron Router guide, Our onBeforeAction hook function will run before our route function when the user navigates to "/admin". If the user is not logged in, the route function will never get called and the AdminPage will not render to the page.
Well, it certainly looks like the route function is being called before the OnBeforeAction considering that debugger stops first to waitOn for a Meteor subscription. Since this subscription requires an admin user to be logged in on the server, if I press continue on debugger the server console logs "no user" and the loading screen goes on forever and forever. The actual OnBeforeAction function for requireLogin never gets called.
waitOn is called before OnBeforeAction. This behavious is corect. From the iron-router docs:
Another alternative is to use waitOn instead of subscribe. This has the same effect but automatically short-circuits your route action and any before hooks (see below), and renders a loadingTemplate instead.
Source
For handling subscriptions you can use 'subscriptions' option:
Router.route('/post/:_id', {
subscriptions: function() {
// returning a subscription handle or an array of subscription handles
// adds them to the wait list.
return Meteor.subscribe('item', this.params._id);
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Source