If i visit the /whatever/:_id/result page nothing is gonna be rendered except the static text. Why doenst my Meteor.subscription work? Is there anything i am missing here?
Router.route('/whatever/:_id/result', {
template: 'result',
name: 'result',
data: function(){
var currentId = this.params._id;
console.log(currentId);
return Questions.findOne({_id:currentId});
},
WaitOn: function(){
var currentId = this.params._id;
return [ Meteor.subscribe('arguments', currentId), Meteor.subscribe('questions',currentId) ]
},
onBeforeAction: function(){
this.next();
}
});
On my server ive got:
Meteor.publish('arguments', function(currentId){
return Arguments.find({decisionId:currentId});
});
Meteor.publish('questions', function(currentId){
return Questions.find({_id:currentId});
});
I think this is a simple typo.
WaitOn: ...
Should be
waitOn: ...
with a lowercase w.
Related
I am trying to get two variable from the iron router so that I can essentially call them in my temlate such as {{user.telephone}} and {{pic.profilepic}}.
My code in the router is as follows. (Does Not Work!)
Router.route('/profile/:_id', {
name: 'profile',
template: 'profile',
data:function(){
return {
user:Info.findOne({_id:this.params._id}),
pic: Profilepics.findOne({_id:this.params._id})
};
},
subscriptions: function(){
return {
Meteor.subscribe("userInfo"),
Meteor.subscribe( "profilepic");
},
action:function (){
if (this.ready()){
this.render();
}else {
this.render('loading');
}
}
});
I able to do just one variable with the following code. i.e get {{user.telephone}}. Any chance anyone can help me get both variable instead of just one?
enterRouter.route('/profile/:_id', {
name: 'profile',
template: 'profile',
data:function(){
return {
user:Info.findOne({_id:this.params._id})
};
},
subscriptions: function(){
return Meteor.subscribe("userInfo")
},
action:function (){
if (this.ready()){
this.render();
}else {
this.render('loading');
}
}
});
If you are using the latest version of iron router, i suggest you update the code to something a bit more modern.
First you create a general app controller:
ApplicationController = RouteController.extend({
layoutTemplate: 'DefaultLayout',
loadingTemplate: 'loading_template',
notFoundTemplate: '404_template',
});
Then you start to extend it for different purposes:
ProfileController = ApplicationController.extend({
show_single: function() {
this.render('profile');
}
});
After this you can create your routes for the profile part
Router.route('/profile/:_id', {
controller: 'ProfileController',
action: 'show_single',
waitOn: function() {
return [
Meteor.subscribe("userInfo"),
Meteor.subscribe("profilepic")
];
},
subscriptions: function() {
this.subscribe("somethingyoudontneedtowaitfor", this.params._id);
},
data: function() {
if (this.ready()) {
return {
user: Info.findOne({
_id: this.params._id
}),
pic: Profilepics.findOne({
_id: this.params._id
})
};
}
}
});
It might be a bit more code, but it gives you complete control over what it does. Also, using this, while the router is waiting for the subscriptions to be ready, it displays the loading template defined above. If you don't want to display the loading, you move the subscriptions out of waiton.
Return multiple subscriptions as an array from subscriptions function,
Use return [
Meteor.subscribe("userInfo"),
Meteor.subscribe( "profilepic")
];
instead of
return { Meteor.subscribe("userInfo"), Meteor.subscribe( "profilepic"); which has {} mismatch.
In a similar situation I have
data: function() {
var gridId = this.params._gridId;
return (People.find({gridId: gridId})
&& GridLog.find({gridId: gridId}));
},
with the subscribe calls in the waitOn() handler as part of the Boolean status
waitOn: function() {
return (Meteor.subscribe('people', this.params._gridId)
&& Meteor.subscribe('gridlog', this.params._gridId) );
},
You may have more success just &&ing them together in waitOn() (as strange as that seems).
I am building a forum with Meteor and would like to display comments to answers. And the answers are on the same page as an individual question. I am able to save the comments once submitted (I can check the mongo database and I see that I am successfully submitting comments), but I'm not able to display them using templates. I would think the problem has something to do with publications and subscriptions, but I can't locate the error. Below are the relevant snippets of code.
answer_item.js
Template.answerItem.helpers({
submittedText: function() {
return this.submitted.toString();
},
comments: function() {
return Comments.find({answerId: this._id});
}
});
answer_item.html
<template name="answerItem">
...
<ul class="comments">
{{#each comments}}
{{> commentItem}}
{{/each}}
</ul>
...
</template>
comment_item.html
<template name="commentItem">
<li>
<h4>
<span class="author">{{author}}</span>
<span class="date">on {{submitted}}</span>
</h4>
<p>{{body}}</p>
</li>
</template>
comment_item.js
Template.commentItem.helpers({
submittedText: function() {
return this.submitted.toString();
}
});
lib/collections/comment.js
Comments = new Mongo.Collection('comments');
Meteor.methods({
commentInsert: function(commentAttributes) {
check(this.userId, String);
check(commentAttributes, {
answerId: String,
body: String
});
var user = Meteor.user();
var answer = Answers.findOne(commentAttributes.answerId);
if (!answer)
throw new Meteor.Error('invalid-comment', 'You must comment on an answer');
comment = _.extend(commentAttributes, {
userId: user._id,
author: user.username,
submitted: new Date()
});
Answers.update(comment.answerId, {$inc: {commentsCount: 1}});
comment._id = Comments.insert(comment);
return comment._id
}
});
router.js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() {
return [Meteor.subscribe('notifications')]
}
});
QuestionsListController = RouteController.extend({
template: 'questionsList',
increment: 5,
questionsLimit: function() {
return parseInt(this.params.questionsLimit) || this.increment;
},
findOptions: function() {
return {sort: this.sort, limit: this.questionsLimit()};
},
subscriptions: function() {
this.questionsSub = Meteor.subscribe('questions', this.findOptions());
},
questions: function() {
return Questions.find({}, this.findOptions());
},
data: function() {
var self = this;
return {
questions: self.questions(),
ready: self.questionsSub.ready,
nextPath: function() {
if (self.questions().count() === self.questionsLimit())
return self.nextPath();
}
};
}
});
NewQuestionsController = QuestionsListController.extend({
sort: {submitted: -1, _id: -1},
nextPath: function() {
return Router.routes.newQuestions.path({questionsLimit: this.questionsLimit() + this.increment})
}
});
FollowedQuestionsController = QuestionsListController.extend({
sort: {follows: -1, submitted: -1, _id: -1},
nextPath: function() {
return Router.routes.followedQuestions.path({questionsLimit: this.questionsLimit() + this.increment})
}
});
Router.route('/', {
name: 'home',
controller: NewQuestionsController
});
Router.route('/new/:questionsLimit?', {name: 'newQuestions'});
Router.route('/followed/:questionsLimit?', {name: 'followedQuestions'});
Router.route('/questions/:_id', {
name: 'questionPage',
waitOn: function() {
return [
Meteor.subscribe('singleQuestion', this.params._id),
Meteor.subscribe('answers', this.params._id),
Meteor.subscribe('comments', this.params._id)
];
},
data: function() { return Questions.findOne(this.params._id); }
});
Router.route('/questions/:_id/edit', {
name: 'questionEdit',
waitOn: function() {
return Meteor.subscribe('singleQuestion', this.params._id);
},
data: function() { return Questions.findOne(this.params._id); }
});
Router.route('/submit', {name: 'questionSubmit'});
var requireLogin = function() {
if (! Meteor.user()) {
if (Meteor.loggingIn()) {
this.render(this.loadingTemplate);
} else {
this.render('accessDenied');
}
} else {
this.next();
}
}
Router.onBeforeAction('dataNotFound', {only: 'questionPage'});
Router.onBeforeAction(requireLogin, {only: 'questionSubmit'});
server/publications.js
Meteor.publish('comments', function(answerId) {
check(answerId, String);
return Comments.find({answerId: answerId});
});
It looks like you need to run a separate query in your comment publication, to get a list of all of the answers for the given question, then use the results of that query to get a list of all the comments for all of the answers.
Meteor.publish('comments', function(questionId) {
check(questionId, String);
var answerIds = _.pluck(Answers.find({'questionId': questionId}, {fields: {_id: 1}}).fetch(), '_id');
return Comments.find({answerId: {$in: answerIds});
});
EDIT
I have a similar feature within an app that I'm working on now, with the same issue you were running into. I spent a few hours on it yesterday and came to the conclusion that the issue has to do with the fact that the _.pluck statement converts the results from the Answers cursor to an array, which prevents the publish function from being reactive.
After looking into several solutions the best one I found was the publish composite package. The syntax is a little verbose, but it gets the job done. To make it all work properly you need to merge all of the publish functions for the question, answers, and comments all into one publish function. Under the covers it creates observeChanges watchers on each of the answers under the question, so it can be reactive.
Meteor.publishComposite('question', function(questionId) {
return {
find: function() {
return Questions.find({_id: questionId});
},
children: [
{
find: function(question) {
return Answers.find({questionId: question._id});
},
children: [
{
find: function(answer, question) {
return Comments.find({answerId: answer._id});
}
}
]
}
]
}
});
I have db with posts and all of them have bollean flagged
I have one template and navigation like (Read, Dont Read).
Problem is that I see all posts (when I must see posts with flagged false or flagged true), and I dont understand why, I think problem in publish/subscribe
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
});
AllPostsController = RouteController.extend({
template: 'timeTable',
waitOn: function() {
return Meteor.subscribe('allPosts');
}
});
readPostController = AllPostsController.extend({
waitOn: function() {
return Meteor.subscribe('readPosts');
}
});
dontreaderPostController = AllPostsController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
}
});
Router.map(function() {
this.route('timeTable', {path: '/',
controller: AllPostsController
});
this.route('readPosts', {path: '/read',
controller: readPostsController
});
this.route('dontreaderPosts', {
path: '/dontreader',
controller: dontreaderPostController
});
});
Meteor.publish('allPosts', function(){
return Posts.find({},{ sort: { createdAt: -1 }});
});
Meteor.publish('readPosts', function(){
return Posts.find({read:true},{ sort: { createdAt: -1 }});
});
Meteor.publish('dontreadPosts', function(){
return Posts.find({read:false},{ sort: { createdAt: -1 }});
});
If someone need more code, Just ask me
Anybody help
EDIT : David solved problem for regular tasks. Main problem that I have specific return Posts.find(...) in my tamplate helper.
<template name="timeTable">
{{#if posts_exist_week}}
{{> table posts=week}}
{{/if}}
{{#if posts_exist_month}}
{{> table posts=month}}
{{/if}}
</template>
<template name="table">
<table class="main-table table">
{{#each posts}}
{{> post}}
{{/each}}
</table>
</template>
You solved my problem if I did not have template timeTable (that show posts for last week and month) Because here it Template helper
Template.timeTable.helpers({
week: function() {
//...
return Posts.find({createdAt: {$gte: weekstart, $lt: yesterday}},{ sort: { createdAt: -1 }}); //return posts that was created in this week
},
month: function() {
//...
return Posts.find({createdAt: {$gte: monthstart, $lte: weekstart}},{ sort: { createdAt: -1 }});
}
});
And now you see that if I choose your decision (David) I will have 2 !!
return
first - in router
second - in template helper
I recreated this locally and found that extend causes the parent controller's waitOn to run. So whenever you go to the /read route it will actually activate both subscriptions and you'll end up with all of the documents on your client. A simple fix is to refactor your controllers like so:
PostController = RouteController.extend({
template: 'timeTable'
});
AllPostsController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('allPosts');
}
});
readPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('readPosts');
}
});
dontreaderPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
}
});
That being said, you don't want to build your app in a way that it breaks when extra subscriptions happen to be running. I would rewrite the controllers to select only the documents that pertain to them. For example:
dontreaderPostController = PostController.extend({
waitOn: function() {
return Meteor.subscribe('dontreadPosts');
},
data: {selector: {read: false}}
});
And now your helpers can use the selector like this:
Template.timeTable.helpers({
week: function() {
var selector = _.clone(this.selector || {});
selector.createdAt = {$gte: weekstart, $lt: yesterday};
return Posts.find(selector, {sort: {createdAt: -1}});
}
});
Also note that sorting in the publish functions may not be useful - see common mistakes.
I have a iron route that searches for a collection item based on the url param. If it finds it, it returns the item as a data context, otherwise it renders a notFound template. The code looks like this:
this.route('profileView', {
path: list_path + '/profiles/:_id',
fastRender: true,
waitOn: function() {
if (Meteor.user()) {
return [Meteor.subscribe('singleProfile', this.params._id, Session.get("currentListId"))];
}
},
data: function() {
var profile = Profiles.findOne({
_id: this.params._id
});
if (!profile) {
this.render("notFound");
} else
return profile;
}
});
The problem is the notFound template gets loaded briefly prior to profile getting returned, although I thought the waitOn function would have handled that. What's the correct pattern to have the desired result using iron router? Thanks.
Is it possible that you forgot to configure the loading and dataNotFound hooks?
Router.onBeforeAction('loading');
Router.onBeforeAction('dataNotFound');
If you want to understand what is actually going on here, please look here.
I had to check for this.ready() in data. Updated code
this.route('profileView', {
path: list_path + '/profiles/:_id',
fastRender: true,
waitOn: function() {
if (Meteor.user()) {
return [Meteor.subscribe('singleProfile', this.params._id, Session.get("currentListId"))];
}
},
data: function() {
if(this.ready()){
var profile = Profiles.findOne({
_id: this.params._id
});
if (!profile) {
this.render("notFound");
} else
return profile;
}
}
});
I have autopublish on.
Template.play.helpers ({
title: function () {
wcount = Workouts.find().count();
console.log(wcount);
}
});
This gives me 0.
But if I'm in the developer console of my browser.
Workouts.find().count()
24
I have recently upgraded to 0.8
If I add waitOn to the Router then I get this behavior every other time I load the page.
Router.map(function() {
this.route('splash', {path: '/'});
this.route('play', {
path: '/play',
template: 'play',
waitOn: function() {
return Meteor.subscribe('Workouts')
}
});
After some help from #Christian Fritz it seems that my problem is that its not waiting on my subscription and if the helper doesn't return anything because its undefined then it doesn't get rerun when the data does get loaded.
I have now turned off autopublish. My server/publications.js is:
Meteor.publish('workouts', function() {
return Workouts.find();
});
My router is:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading'
});
Router.map(function() {
this.route('splash', {path: '/'});
this.route('play', {
path: '/play',
template: 'play',
waitOn: function() {
return Meteor.subscribe('workouts')
}
});
});
in play.js
var workoutsSubcription = Meteor.subscribe('workouts');
console.log("workoutsSubscription.ready() is ",workoutsSubcription.ready());
returns:
workoutsSubscription.ready() is false
once or twice then finally reruns when its fully loaded as true. But shouldn't the waitOn mean that it doesn't run that page until the data is there?
You probably want something like this:
var workoutsSubcription = Meteor.subscribe('workout_count');
Template.play.helpers ({
title: function () {
if(workoutsSubcription.ready()){
wcount = Workouts.find().count();
}
}
});
You would need to publish your subscription on the server as well
Meteor.publish("workout_count", function () {
return Workouts.find();
});