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.
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 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.
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.
I have a Meteor template that should be displaying some data.
Template.svg_template.rendered = function () {
dataset_collection = Pushups.find({},{fields: { date:1, data:1 }}, {sort: {date: -1}}).fetch();
a = moment(dataset_collection[0].date, "YYYY/M/D");
//more code follows that is also dependent on the collection being completely loaded
};
Sometimes it works, sometimes I get this error:
Exception from Deps afterFlush function: TypeError: Cannot read property 'date' of undefined
I'm not using Deps in any context. As I understand it, the collection is being referenced before it is completely finished loading.
I therefore would like to figure out how to simply say "wait until the collection is found before moving on." Should be straightforward, but can't find an updated solution.
You are right, you should ensure that code depending on fetching the content of a client-side subscribed collection is executed AFTER the data is properly loaded.
You can achieve this using a new pattern introduced in Meteor 1.0.4 : https://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe
client/views/svg/svg.js
Template.outer.onCreated(function(){
// subscribe to the publication responsible for sending the Pushups
// documents down to the client
this.subscribe("pushupsPub");
});
client/views/svg/svg.html
<template name="outer">
{{#if Template.subscriptionsReady}}
{{> svgTemplate}}
{{else}}
Loading...
{{/if}}
</template>
In the Spacebars template declaration, we use an encapsulating outer template to handle the template level subscription pattern.
We subscribe to the publication in the onCreated lifecycle event, and we use the special reactive helper Template.subscriptionsReady to only render the svgTemplate once the subscription is ready (data is available in the browser).
At this point, we can safely query the Pushups collection in the svgTemplate onRendered lifecycle event because we made sure data made its way to the client :
Template.svgTemplate.onRendered(function(){
console.log(Pushups.find().fetch());
});
Alternatively, you could use the iron:router (https://github.com/iron-meteor/iron-router), which provides another design pattern to achieve this common Meteor related issue, moving subscription handling at the route level instead of template level.
Add the package to your project :
meteor add iron:router
lib/router.js
Router.route("/svg", {
name: "svg",
template: "svgTemplate",
waitOn: function(){
// waitOn makes sure that this publication is ready before rendering your template
return Meteor.subscribe("publication");
},
data: function(){
// this will be used as the current data context in your template
return Pushups.find(/*...*/);
}
});
Using this simple piece of code you'll get what you want plus a lot of added functionalities.
You can have a look at the Iron Router guide which explains in great details these features.
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md
EDIT 18/3/2015 : reworked the answer because it contained outdated material and still received upvotes nonetheless.
This is one of those problems that I really wish the basic meteor documentation addressed directly. It's confusing because:
You did the correct thing according to the API.
You get errors for Deps which doesn't point you to the root issue.
So as you have already figured out, your data isn't ready when the template gets rendered. What's the easiest solution? Assume that the data may not be ready. The examples do a lot of this. From leaderboard.js:
Template.leaderboard.selected_name = function () {
var player = Players.findOne(Session.get("selected_player"));
return player && player.name;
};
Only if player is actually found, will player.name be accessed. In coffeescript you can use soaks to accomplish the same thing.
saimeunt's suggestion of iron-router's waitOn is good for this particular use case, but be aware you are very likely to run into situations in your app where the data just doesn't exist in the database, or the property you want doesn't exist on the fetched object.
The unfortunate reality is that a bit of defensive programming is necessary in many of these cases.
Using iron-router to wait on the subscription works, but I like to keep subscriptions centrally managed in something like a collections.js file. Instead, I take advantage of Meteor's file load order to have subscriptions loaded before everything else.
Here's what my collections.js file might look like:
// ****************************** Collections **********************************
Groups = new Mongo.Collection("groups");
// ****************************** Methods **************************************
myGroups = function (userId) {
return Groups.find({"members":{$elemMatch:{"user_id":userId}}});
};
// ****************************** Subscriptions ********************************
if(Meteor.isClient){
Meteor.subscribe("groups");
}
// ****************************** Publications *********************************
if(Meteor.isServer){
Meteor.publish("groups", function () {
return myGroups(this.userId);
});
}
I then put collections.js into a lib/ folder so that it will get loaded prior to my typical client code. That way the subscription is centralized to a single collections.js file, and not as part of my routes. This example also centralizes my queries, so client code can use the same method to pull data:
var groups = myGroups(Meteor.userId());
I'm currently evaluating Ember.js and therefore I am building a small sample app. Currently everything went quite smooth so far, but now I don't seem to be able to fix my last little problem.
When I access the app normaly via the route films, everything works as expected. The list of films is displayed. Now when I click onto a film, the details of the film are loaded via setupController hook just below the list of films. That's all just fine.
Here comes my problem: I would like to be able to access the film details directly via url, but somehow in this case another request is fired to grab the film details, with the value of undefinded. As far as I understand that is the model hook.
I can only guess, but I think it is the model hook which is beeing executed.
Can someone point me to the probably obvious mistake I'm making? And on the other hand, is the code i wrote so far "correct"? Or is there a better way of doing this?
(I am aware of the bad way I use to render the film details. I will remove the {{#each}} tag, and change the way I asign the response to the film variable.
Here the link to the sample app: http://jsbin.com/ewiN/1#/films
UPDATE
Ok, now I am getting really confused. I almost have it working, hopefully someone can point it out to me, because it's such a simple task, but it nearly seems impossible to do without knowing ember really well...
When accessing the app via url, it only works when I remove the setupController hook. But I need that hook, to load the FilmDetails on clicking onto the links to properly load the FilmDetails.
http://jsbin.com/ewiN/16#/films/tt0100669
Many thanks for the feedback!
Regards
Reto
Instead of use jQuery.getJSON, use Ember.RSVP.Promise, because internally ember use this promise api instead of jquery. I think that using both, can make inconsistencies.
return new Ember.RSVP.Promise(function(resolve, reject) {
var films = [];
jQuery.getJSON("http://www.omdbapi.com/?s=" + searchTerm, function (response) {
$.each(response.Search, function (index, value) {
films.pushObject(Kitag.Films.create({
title: value.Title,
id: value.imdbID
}));
});
}).fail(reject);
resolve(films);
});
Because we are returning a promise instead of an object, we need to use the model hook, because it is wait until the promise is resolve to render templates.
Kitag.FilmsRoute = Ember.Route.extend({
model: function() {
return Kitag.Films.getMovies('spiderman');
}
});
I have removed the Kitag.FilmRoute, because the expected:
Kitag.FilmRoute = Ember.Route.extend({
model: function(params) {
return Kitag.Film.find(params.id)
},
serialize: function (model) {
return { film_id: model.get("id") };
}
});
is the default.
This is the final result http://jsbin.com/ewiN/15/edit