I may be using a totally incorrect approach for my problem and if so please tell me
My Meteor app collects emails address and emails them a link to a download page with a token. This download page is a Iron Router route and the token is the ID of an item in a collection. The token is checked for prior use and then a download will be initiated [that part not written yet]. So I have this route:
this.route('download', {
path: '/download/:_id',
template: 'capture_download',
waitOn: function () {
return Meteor.subscribe('captures');
},
data: function() { return Captures.findOne(this.params._id); }
});
So I need to trigger a call to my server method that does the checking logic as soon as this route is loaded. And I need the ID value to make that call. So I have this:
Template.capture_download.rendered = function(template) {
Meteor.call('claimDownload', this.data._id, function(err, result) {
// callback logic here
});
}
What I don't understand is that this only sometimes works. Sometimes the call happens with the ID value correct. Other times I get:
Exception from Deps afterFlush function function: TypeError: Cannot read property '_id' of null
So I'm thinking that either my template event [rendered] is wrong [I can't find in the docs a list of template events anywhere], or that I need to do something to wait for a valid this value, or that my approach is totally wrong. How would I fix this occasional lack of data in the view when rendered?
Use onBeforeAction within your Iron Router route, rather than a rendered method in the template:
this.route('download', {
path: '/download/:_id',
template: 'capture_download',
waitOn: function () {
return Meteor.subscribe('captures');
},
data: function() { return Captures.findOne(this.params._id); },
onBeforeAction: function() {
Meteor.call('claimDownload', this.params._id, function(err, result) {
// callback logic here
});
}
});
See https://github.com/EventedMind/iron-router/blob/dev/DOCS.md#before-and-after-hooks. Your “checking for token prior use” sounds a lot like the “checking that the user is logged in” example in the docs, which is solved with onBeforeAction.
Related
I have a template that has data getting passed into it through the Iron Router params here, it might be obvious what the template is being designed to do:
lib/routes.js
// custom reset password page for child user
Router.route('/reset-password/child/:_id', {
name: 'reset-child-password',
template: 'childResetPassword',
layoutTemplate: 'appLayout',
data: function() {
return Users.findOne({ _id: this.params._id });
}
});
But, when I try to access this child user data in the template I get errors saying this.data is undefined. or cannot read property 'profile' of undefined. Here are my helpers and template use of the helper.
client/templates/childResetPassword.html
...
<h3>Reset Password for {{childFirstName}}</h3>
<form id='childResetPassword'>
<div class="form-group">
<input type="password" name="new-child-password" class="form-control" value=''>
</div>
...
client/templates/helpers/childResetPassword.js
Template.childResetPassword.helpers({
childFirstName: function() {
console.log("Template helpers:");
console.log(this.data);
return this.data.profile.firstname;
}
});
Any thoughts on how to access the data-context passed with the iron router data callback? Am I using it incorrectly?
UPDATE (STILL NOT ANSWERED): I have verified that this particular user that I am passing into the template data-context is being found and that their profile is populated with a firstname property, and I'm still getting the same error.
Based on another question I found I tried this. I added a Template rendered callback function like this:
client/templates/helpers/childResetPassword.js
Template.childResetPassword.rendered = function() {
console.log(this);
};
I do see this.data containing the correct user object in the browser console, but my this.data.profile.firstname still fails, yet again, with the same console output error. If there something I need to do between the template rendering and the template helper?? SO CONFUSED!!!
You don't have to mention data ... you can just call this.profile.firstname. Your application already understands 'this' as the data object that is returned.
Template.childResetPassword.helpers({
childFirstName: function() {
return this.profile.firstname;
}
});
So, #Joos answer is not wrong, however after a lot more trial and error I found the solution for the meteor project I am working on.
My project (unbeknownst to me until I looked around more) has the meteor package autopublish removed. Therefore in order to access data in my collections I have to be subscribed to them. So the best place I put this subscription line is within my Router.route declaration for this template:
Router.route('/reset-password/child/:_id', {
name: 'reset-child-password',
template: 'childResetPassword',
layoutTemplate: 'appLayout',
waitOn: function() { // this is new new line/option i added to my route
return Meteor.subscribe('users');
},
data: function() {
if (this.ready()) {
var childUser = Users.findOne({_id: this.params._id});
if (childUser)
return childUser;
else
console.error("Child User not found", childUser);
}
else {
this.render("Loading");
}
}
});
So with that being said, if you still have autopublish package in your project and you intending to keep it, then #Joos answer is all you need to do.
However, if you DO intend to remove it, you need my router solution above, combined with making sure that you have published your users collection like this somewhere on the server:
server/publications.js
Meteor.publish("users", function () {
return Meteor.users.find();
});
I'm trying to implement a basic route using Flow Router. But no matter what _id of a collection document I request; I always only get the info about the first document in my collection - 'Requests'.
So here's my route definition in the file /lib/routes.js:
FlowRouter.route('/requests/:reqId', {
subscriptions: function (params, queryParams) {
this.register('theRequest', Meteor.subscribe('singleRequest', params.reqId));
},
action: function (params, queryParams) {
FlowLayout.render('layout', { aside: 'aside', main: 'singleRequest' });
console.log("Yeah! We are on the post:", params.reqId);
},
name: 'aRequest'
});
Here's my helper:
Template.singleRequest.helpers({
getRequest: function () {
return Requests.findOne();
}
});
Here's my server publish:
Meteor.publish('singleRequest', function (reqId) {
return Requests.find({ _id: reqId});
});
And here's the template:
<template name="singleRequest">
{{#if isSubReady 'theRequest'}}
{{#with getRequest}}
<h2>{{_id}}</h2>
<p>{{reqFrom}}</p>
<p>{{reqBy}}</p>
{{/with}}
{{else}}
loading...
{{/if}}
</template>
What am I doing wrong?
Note: In the console, I do see right 'reqId' slug due to the console.log command in the route definition. But I do not see the relevant info for the document which it belongs to.
Thanks!
OK my problem was that I previously had another subscription where I published all the Requests - not just the one with that certain _id. And because I did not create the helper to get only the one with that certain _id; of course server just sent me the very first request.
My solution was only to subscribe to previous subscription and define in the helper to fetch to the request _id:
FlowRouter.route('/requests/:reqId', {
subscriptions: function (params, queryParams) {
this.register('allRequests', Meteor.subscribe('Requests', params.reqId));
},
action: function (params, queryParams) {
FlowLayout.render('layout', { aside: 'aside', main: 'singleRequest' });
console.log("Yeah! We are on the post:", params.reqId);
},
name: 'aRequest'
});
and the helper:
Template.singleRequest.helpers({
getRequest: function () {
var reqId = FlowRouter.getParam("reqId")
return Requests.findOne({_id:reqId});
}
});
For anyone who browses to this question looking for how to get Flow Router to capture and dynamically link to slugs from the db, then call a template page for each item, I made a very simple example app and posted it on here on GitHub.
Hope it will help someone.
I believe your code is correct for what you are trying to do. However, I infer from your direct replication of the code from flow-router that you're pretty new to Meteor. Therefore, I'm willing to take a punt that you still have the autopublish package in your app.
Autopublish pushes all data in your collection to the client. Even without a publish/subscribe call.
You can do two things. To keep autopublish (which will make your development process easier at the start but maybe harder later on), just change your template helper to:
Template.singleRequest.helpers({
getRequest: function () {
return Requests.findOne({ _id: FlowRouter.getParam("reqId")});
}
});
Alternatively (and you will want to do this eventually), go the the command line and type:
meteor remove autopublish
You can read up on the pros and cons of autopublish in lots of places to make your own decision on which option is best for you. However, you should also consider whether or not you want to cache your data in future (e.g. using subsManager) so you may want to change your template helper regardless.
EDIT: Here is the github repo. And you can test the site here.
On the homepage, just open the browser console and you will notice that WaitOn and data are being run twice. When there is no WaitOn, then the data just runs once.
I have setup my pages by extending RouteController and further extending these controllers. For example:
ProfileController = RouteController.extend({
layoutTemplate: 'UserProfileLayout',
yieldTemplates: {
'navBarMain': {to: 'navBarMain'},
'userNav': {to: 'topUserNav'},
'profileNav': {to: 'sideProfileNav'}
},
// Authentication
onBeforeAction: function() {
if(_.isNull(Meteor.user())){
Router.go(Router.path('login'));
} else {
this.next();
}
}
});
ProfileVerificationsController = ProfileController.extend({
waitOn: function() {
console.log("from controller waitOn");
return Meteor.subscribe('userProfileVerification');
},
data: function() {
// If current user has verified email
console.log("from controller data start");
var verifiedEmail = Meteor.user().emails && Meteor.user().emails[0].verified ? Meteor.user().emails[0].address : '';
var verifiedPhoneNumber = Meteor.user().customVerifications.phoneNumber && Meteor.user().customVerifications.phoneNumber.verified ? Meteor.user().customVerifications.phoneNumber.number : '';
var data = {
verifiedEmail: verifiedEmail,
verifiedPhoneNumber: verifiedPhoneNumber
};
console.log("from controller data end");
return data;
}
});
On observing the console in the client, it seems the hooks are being run 2-3 times. And I also get an error on one of the times because the data is not available. The following is the console on just requesting the page once:
from controller waitOn
profileController.js?966260fd6629d154e38c4d5ad2f98af425311b71:44 from controller data start
debug.js:41 Exception from Tracker recompute function: Cannot read property 'phoneNumber' of undefined
TypeError: Cannot read property 'phoneNumber' of undefined
at ProfileController.extend.data (http://localhost:3000/lib/router/profileController.js?966260fd6629d154e38c4d5ad2f98af425311b71:46:62)
at bindData [as _data] (http://localhost:3000/packages/iron_controller.js?b02790701804563eafedb2e68c602154983ade06:226:50)
at DynamicTemplate.data (http://localhost:3000/packages/iron_dynamic-template.js?d425554c9847e4a80567f8ca55719cd6ae3f2722:219:50)
at http://localhost:3000/packages/iron_dynamic-template.js?d425554c9847e4a80567f8ca55719cd6ae3f2722:252:25
at null.<anonymous> (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:2445:26)
at http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1808:16
at Object.Blaze._withCurrentView (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:2043:12)
at viewAutorun (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1807:18)
at Tracker.Computation._compute (http://localhost:3000/packages/tracker.js?517c8fe8ed6408951a30941e64a5383a7174bcfa:296:36)
at Tracker.Computation._recompute (http://localhost:3000/packages/tracker.js?517c8fe8ed6408951a30941e64a5383a7174bcfa:310:14)
from controller data start
from controller data end
from controller waitOn
from controller data start
from controller data end
Have I not used the controllers properly?
Without being able to see the rest of the code that you have defined that uses these route controllers (such as templates or route definitions), I cannot accurately speak to the reason for the data function being called multiple times. I suspect that you may be using the ProfileVerificationsController with multiple routes, in which case the data definition for this controller would be executed multiple times, one for each route that uses the controller. Since the data definition is reactive, as you browse through your application and data changes, this might be resulting in the code defined to be rerun.
As for your controller definitions, I would suggest making a few modifications to make the code more robust and bulletproof. First, the ProfileController definition:
ProfileController = RouteController.extend({
layoutTemplate: 'UserProfileLayout',
yieldRegions: {
'navBarMain': {to: 'navBarMain'},
'userNav': {to: 'topUserNav'},
'profileNav': {to: 'sideProfileNav'}
},
onBeforeAction: function() {
if(!Meteor.user()) {
Router.go(Router.path('login'));
this.redirect('login'); // Could do this as well
this.render('login'); // And possibly this is necessary
} else {
this.next();
}
}
});
Notice the first thing that I changed, yieldTemplates to yieldRegions. This typo would prevent the regions from your templates using this route controller to be properly filled with the desired subtemplates. Second, in the onBeforeAction definition, I would suggest checking not only whether or not the Meteor.user() object is null using Underscore, but also checking for whether or not it is undefined as well. The modification that I made will allow you to check both states of the Meteor.user() object. Finally, not so much a typo correction as an alternative suggestion for directing the user to the login route, you could use the this.redirect() and this.render() functions instead of the Router.go() function. For additional information on all available options that can be defined for a route/route controller, check this out.
Now for the ProfileVerificationsController definition:
ProfileVerificationsController = ProfileController.extend({
waitOn: function() {
return Meteor.subscribe('userProfileVerification');
},
data: function() {
if(this.ready()) {
var verifiedEmail = Meteor.user().emails && Meteor.user().emails[0].verified ? Meteor.user().emails[0].address : '';
var verifiedPhoneNumber = Meteor.user().customVerifications.phoneNumber && Meteor.user().customVerifications.phoneNumber.verified ? Meteor.user().customVerifications.phoneNumber.number : '';
var data = {
verifiedEmail: verifiedEmail,
verifiedPhoneNumber: verifiedPhoneNumber
};
return data;
}
}
});
Notice the one thing that I changed, which is to wrap all of your code defined in the data option for your controller with a if(this.ready()){}. This is critical when using the waitOn option because the waitOn option adds one or more subscription handles to a wait list for the route and the this.ready() check returns true only when all of the handles in the wait list are ready. Making sure to use this check will prevent any cases of data unexpectedly not being loaded yet when you are building up your data context for the route. For additional information on defining subscriptions for your routes/route controllers, check this out.
As a final suggestion, for your onBeforeAction option definition in your ProfileController, I would suggest moving this out into its own global hook like so:
Router.onBeforeAction(function() {
if(!Meteor.user()) {
Router.go(Router.path('login'));
} else {
this.next();
}
});
Defining this check in the global hook ensures that you don't have to worry about adding your ProfileController to all of your routes just to make sure that this check is run for all of them. The check will be run for every route every time that one is accessed. Just a suggestion, though, as you may have reasons for not doing this. I just wanted to suggest it since I make sure to do it for every Meteor app that I develop for additional security.
In my application I want to read the parameters user is entering and then I want to use that parameter. http://responsive.beta.postify.com/X I want to read that X value. But first how do I ensure that the router expects a parameter?
My router is like this
Cards.Router.map(function ()
{
this.resource('cards', {path: '/'}, function ()
{
// additional child routes
this.resource('selectImage');
this.resource('message');
this.resource('recipient');
this.resource('orderStatus');
this.resource('thankyou');
this.resource('accountInfo');
this.resource('recentOrders');
this.resource('howTo');
this.resource('faq');
});
});
I want that parameter whenever the app loads. That is going to be my clientID which I would be using to fetch data from server depending upon the client.
Any thoughts on it?
When I do something like this
Cards.Router.map(function ()
{
this.resource('cards', {path: ':clientID'}, function ()
{
// additional child routes
this.resource('selectImage');
this.resource('message');
this.resource('recipient');
this.resource('orderStatus');
this.resource('thankyou');
this.resource('accountInfo');
this.resource('recentOrders');
this.resource('howTo');
this.resource('faq');
});
});
and in my browser if I put like this http://responsive.beta.postify.com/#/26 then its working but if I do like http://responsive.beta.postify.com/26 then it is not working.
To answer your question directly, to use a parameter in a route you would do something like this:
this.resource('cards', { path: '/:user_id' });
Then in your route
App.CardsRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('post', params.user_id);
}
});
This is how you can get a parameter in a certain route. Now as far as your application goes, using the code I posted above should get you that parameter as long as they access the root ('/') of your application on first load and have the user_id in the url.
I would suggest a different strategy maybe for getting the client_id and storing it for later user in your application. For example, in my application I have an Ember.Application.initializer({}) where I store the client_id. All depends on your server configuration and how your app is built, but I would definitely try and get the client_id a different way if you can!
Good luck.
New to ember.js. What I'm trying to do is: create a transitional route that has no path, that I can pass an AJAX promise to as the model when I transition to it, and then it makes a redirect decision once the promise completes. I want the LoadingRoute view to be invoked while this is happening. I've tried to accomplish that with the following route:
App.LoginUserRoute = Ember.Route.extend({
setupController: function(controller, model) {
//Model should be a promise
model.then(this.success.bind(this),this.failure.bind(this));
},
success: function (data) {
if (data.success) {
App.loggedInUser = App.User.create(data);
console.log(App.loggedInUser);
//Make redirection decision
}
else {
alert(data.error);
}
},
failure: function () {
//Failure code
}
});
However, when I try to pass the promise to the route like follows:
var request = $.post("api.php", {action: 'create_user', username: this.username, password: this.password});
this.transitionToRoute('loginUser',request);
I get the error "More context objects were passed than there are dynamic segments for the route: loginUser" because I'm trying to create a pathless route and Ember requires that the model be serialized into the path when passed using transitionToRoute().
The reason I need this is:
The login event can happen from multiple controllers (when the user registers, when they login using the login screen, or when the application first loads if the right cookie is detected) and I don't want to duplicate the login logic across all those controllers.
After the login completes, there's multiple different routes the user could then be directed to, depending on the nature of the returned user object.
I want the LoadingRoute to be invoked while the request is completing.
I assume the solution to my problem is not to use routing, but rather something else. However, I'm not sure what the "something else" would be.
You'll want to take advantage of a mixin, and hooking into the transition route.
The following SO answer will work for all of your needs:
Ember Client Side Authentication, route authentication
In the end I achieved what I was trying to do by adding the following code to ApplicationController
loginUser: function (request) {
this.transitionToRoute('loading');
request.then(this.loginRequestSuccess.bind(this),this.loginRequestFailure.bind(this));
},
loginRequestSuccess: function (data) {
if (data.success) {
App.loggedInUser = App.User.create(data.user);
console.log(App.loggedInUser);
//Route transition decision
}
else {
alert(data.error);
}
},
loginRequestFailure: function () {
//Failure code
}
And then calling the loginUser() function with the jqXHR object from wherever in the code a login request was made.