Meteor.js collection aggregate returning undefined is not a function - meteor

I am trying to make collection aggregate in my Meteor.js app as shown below, yet each time I call my server logSummary method I get the following error. Can someone please tell me what I am doing wrong / how to resolve this error? Thanks.
Note: I am using Meteor-aggregate package
TypeError: undefined is not a function
at Object.Template.detailedreport.helpers.myCollection (http://localhost:3000/client/views/report.js?
Code:
Template.detailedreport.rendered = function() {
Session.set("dreport_customer", "");
Session.set("dreport_project", "");
Session.set("dreport_startDate", new Date());
Session.set("dreport_endDate", new Date());
$('.set-start-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-end-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-start-date').on("dp.change",function (e) {
Session.set("dreport_startDate", $('.set-start-date').data('DateTimePicker').getDate().toLocaleString());
});
$('.set-end-date').on("dp.change",function (e) {
Session.set("dreport_endDate", $('.set-end-date').data('DateTimePicker').getDate().toLocaleString());
});
};
Template.detailedreport.helpers({
customerslist: function() {
return Customers.find({}, {sort:{name: -1}});
},
projectslist: function() {
return Projects.find({customerid: Session.get("dreport_customer")}, {sort:{title: -1}});
},
myCollection: function () {
var now = Session.get("dreport_startDate");
var then = Session.get("dreport_endDate");
var custID = Session.get("dreport_customer");
var projID = Session.get("dreport_project");
Meteor.call('logSummary', now, then, projID, custID, function(error, data){
if(error)
return alert(error.reason);
return data;
});
},
settings: function () {
return {
rowsPerPage: 10,
showFilter: true,
showColumnToggles: false,
fields: [
{ key: '0._id.day', label: 'Day' },
{ key: '0.totalhours', label: 'Hours Spent'}
]
};
}
});
Template.detailedreport.events({
'submit form': function(e) {
e.preventDefault();
Session.set('dreport_endDate', $('.set-end-date').data('DateTimePicker').getDate().toLocaleString());
Session.set('dreport_startDate', $('.set-start-date').data('DateTimePicker').getDate().toLocaleString());
Session.set('dreport_project', $(e.target).find('[name=project]').val());
Session.set('dreport_customer', $(e.target).find('[name=customer]').val());
},
'change #customer': function(e){
Session.set("dreport_project", "");
Session.set("dreport_customer", e.currentTarget.value);
},
'change #project': function(e){
Session.set("dreport_project", e.currentTarget.value);
}
});
Template:
<div>
{{> reactiveTable class="table table-bordered table-hover" collection=myCollection settings=settings}}
</div>
Server:
Meteor.methods({
logSummary: function(startDate, endDate, projid, custid){
var pipeline = [
{ $match: { date: { $gte: new Date(startDate), $lte: new Date(endDate) },
projectid: projid,
customerid: custid
}
},
{ $group: {
_id: {
"projectid": "$projectid",
"day": { "$dayOfMonth": "$date" },
"month": { "$month": "$date" },
"year": { "$year": "$date" }
},
totalhours: {"$sum": "$hours"}
}}
];
return ProjectLog.aggregate(pipeline);;
}
});

Looking at the ReactiveTable documentation, it looks like you need to do something like:
Template.myTemplate.helpers({
myCollection: function () {
return myCollection;
}
});
Where myCollection is the name of a Mongo/Meteor collection (e.g. BlogPosts) which you defined with e.g. BlogPosts = new Mongo.Collection('blogPosts');
The reason you're getting undefined is not a function is that you are calling a Meteor method inside a template helper. The call is asynchronous so the return value is undefined. Now you are passing undefined to ReactiveTable. ReactiveTable will be trying to call something like myCollection.find() which is essentially undefined.find() and will therefore throw the error you're seeing.
Later on the Meteor call will finish and the data value will be lost because the function has already returned.
You can call Meteor.call inside the onCreated function like so:
Template.myTemplate.onCreated(function () {
Meteor.call('myFunction', 'my', 'params', function (err, result) {
if (err) {
// do something about the error
} else {
Session.set('myData',result);
}
});
});
Template.myTemplate.helpers({
myData: function () {
Session.get('myData')
}
});
This won't fix the issue with ReactiveTable, however.
If the collection you're trying to display is only used for this single page, you could put the aggregation inside the publish function so that minimongo contains only the documents that match the aggregation and therefore the correct documents will appear in your ReactiveTable.

Related

SimpleSchema insert fails with unhelpful error unless {validate: false} is set

At this point I've actually pared down my code to mimic the example in the docs, and I'm getting the same error.
I've defined my Collection as such:
var Categories = new Mongo.Collection('categories');
const CategorySchema = new SimpleSchema({
title: {
type: String,
label: "Title",
max: 255,
optional: true
},
created: {
type: Date,
label: "Date Category Created",
autoValue: () => {
if(this.isInsert){
return new Date();
} else if (this.isUpsert) {
return {$setOnInsert: new Date()};
}
}
},
updated: {
type: Date,
label: "Date Category Modified",
autoValue: () => {
if(this.isUpdate){
return new Date();
}
},
optional: true
}
});
Categories.attachSchema(CategorySchema);
export { Categories };
The autovalue stuff is borrowed straight from the docs.
And have a method for calling insert:
Meteor.methods({
'categories.insert'(title){
check(title, String);
Categories.insert({title: title}, (err, res) => {
if(err){
console.error("Category insert failed: " + err);
}
});
},
...
As I've tried to get through this issue, I've added and removed rules to the schema definition, sometimes getting a call stack exceeded error. Mostly what I get is the following:
Exception while simulating the effect of invoking 'categories.insert' TypeError: func is not a function
at http://localhost:3000/packages/modules.js?hash=fefb674368e70344875cb9448e3ea784a880ffb0:13027:18
at Array.forEach (native)
at doValidation (http://localhost:3000/packages/modules.js?hash=fefb674368e70344875cb9448e3ea784a880ffb0:13026:17)
at ValidationContext.validate (http://localhost:3000/packages/modules.js?hash=fefb674368e70344875cb9448e3ea784a880ffb0:12580:57)
at ns.Collection.doValidate (http://localhost:3000/packages/aldeed_collection2-core.js?hash=0564817159bbe9f8209b6a192a1fa2b2bbab924a:466:33)
at ns.Collection.Mongo.Collection.(anonymous function) [as insert] (http://localhost:3000/packages/aldeed_collection2-core.js?hash=0564817159bbe9f8209b6a192a1fa2b2bbab924a:260:25)
at ns.Collection.<anonymous> (http://localhost:3000/packages/matb33_collection-hooks.js?hash=d44fd0eb02806747e1bb0bdc3938463e415c63ec:402:19)
at ns.Collection.collection.(anonymous function) [as insert] (http://localhost:3000/packages/matb33_collection-hooks.js?hash=d44fd0eb02806747e1bb0bdc3938463e415c63ec:155:21)
at DDPCommon.MethodInvocation.categories.insert (http://localhost:3000/app/app.js?hash=580bf82f2f196202280f32cf5ffacbc930cd6b10:20073:14)
at http://localhost:3000/packages/ddp-client.js?hash=c9ca22092a3137a7096e8ab722ba5ab8eedb9aec:4076:25 TypeError: func is not a function
at http://localhost:3000/packages/modules.js?hash=fefb674368e70344875cb9448e3ea784a880ffb0:13027:18
at Array.forEach (native)
at doValidation (http://localhost:3000/packages/modules.js?hash=fefb674368e70344875cb9448e3ea784a880ffb0:13026:17)
at ValidationContext.validate (http://localhost:3000/packages/modules.js?hash=fefb674368e70344875cb9448e3ea784a880ffb0:12580:57)
at ns.Collection.doValidate (http://localhost:3000/packages/aldeed_collection2-core.js?hash=0564817159bbe9f8209b6a192a1fa2b2bbab924a:466:33)
at ns.Collection.Mongo.Collection.(anonymous function) [as insert] (http://localhost:3000/packages/aldeed_collection2-core.js?hash=0564817159bbe9f8209b6a192a1fa2b2bbab924a:260:25)
at ns.Collection.<anonymous> (http://localhost:3000/packages/matb33_collection-hooks.js?hash=d44fd0eb02806747e1bb0bdc3938463e415c63ec:402:19)
at ns.Collection.collection.(anonymous function) [as insert] (http://localhost:3000/packages/matb33_collection-hooks.js?hash=d44fd0eb02806747e1bb0bdc3938463e415c63ec:155:21)
at DDPCommon.MethodInvocation.categories.insert (http://localhost:3000/app/app.js?hash=580bf82f2f196202280f32cf5ffacbc930cd6b10:20073:14)
at http://localhost:3000/packages/ddp-client.js?hash=c9ca22092a3137a7096e8ab722ba5ab8eedb9aec:4076:25
meteor.js?hash=27829e9…:930 Error invoking Method 'categories.insert': Internal server error [500]
EDIT
After swapping out the arrow functions:
methods.js
Meteor.methods({
// Settings: create new category
'categories.insert': function(title){
check(title, String);
Categories.insert({title: title}, function(err, res){
if(err){
console.error("Category insert failed: " + err);
}
});
},
...
settings.js
Template.settings.onCreated(function(){
Meteor.subscribe('categories');
});
Template.settings.helpers({
categories: function(){
return Categories.find();
}
});
Template.settings.events({
'click #newCategoryButton': function(e){
let title = $("#newCategoryInput").val();
if(title !== ""){
Meteor.call('categories.insert', title);
$("#newCategoryInput").val("").focus();
}
},
'click .removeCategory': function(e){
var id = $(e.currentTarget).data('id');
Meteor.call('categories.remove', id);
}
});
Categories.js
var Categories = new Mongo.Collection('categories', options);
const CategorySchema = new SimpleSchema({
title: {
type: String,
label: "Title",
max: 255,
optional: true
},
created: {
type: Date,
label: "Date Category Created",
autoValue: function(){
if(this.isInsert){
return new Date();
} else if (this.isUpsert) {
return {$setOnInsert: new Date()};
}
}
},
updated: {
type: Date,
label: "Date Category Modified",
autoValue: function(){
if(this.isUpdate){
return new Date();
}
},
optional: true
}
});
Categories.attachSchema(CategorySchema);
Still getting the same error as in original post
You're using arrow functions like Sean mentioned in the comment. Replace them with traditional function(){...} and you should be good to go.
You can read more about this issue here

Meteor pub/sub issues

This below is my collection code
Competitions = new Mongo.Collection("competitions");
var CompetitionsSchema = new SimpleSchema({
year: {
type: String
},
division: {
type : String,
allowedValues: ['Elite', '1st','2nd','3rd','4th','Intro']
},
teams:{
type : [TeamSchema],
allowedValues: (function () {
return Teams.find().fetch().map(function (doc) {
return doc.name;
});
}()) //here we wrap the function as expression and invoke it
}
});
In the allowedValues function
Teams.find is empty.
In the router I am subscribing to the publication as follows
this.route('competitions', {
path: '/admin/competitions',
layoutTemplate: 'adminLayout',
waitOn: function () {
return [
Meteor.subscribe('teams')
];
}
});
This is my publish function
Meteor.publish('teams', function() {
return Teams.find({},{sort: {
points: -1,
netRunRate : -1
}});
});
Do I have to do subscription some where else as well?
Your problem is in this piece of code:
allowedValues: (function () {
return Teams.find().fetch().map(function (doc) {
return doc.name;
});
}()) //here we wrap the function as expression and invoke it
This gets called when the page loads. At that point the Teams collection will still be empty on the client side. You need to wait until the data is ready. Since you are using waitOn in iron-router, it might be enough to just move this code to the onRendered callback.

Unable to display data from database

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});
}
}
]
}
]
}
});

Need help updating Router.js for upgrade to Meteor 1.0 and iron:router package

I'm in the midst of updating from Meteor 8.2 to Meteor 1.0. I've removed all my old meteorite packages and installed the relevant meteor package system packages. I had to install the new iron-router package and I'm getting the following error in my console on meteor run:
Route dispatch never rendered. Did you forget to call this.next() in an onBeforeAction?
The migration notes for the package say: "onBeforeAction hooks now require you to call this.next(), and no longer take a pause() argument."
I tried following the example by remove pause from the function and adding this.next(); after the else statement, but to no avail.
How to edit my router so it uses the new onBeforeAction hook? Also, anything else you can call out from the migration that might be problematic would be much appreciated. Thanks!
Here's my router file:
/*****************************************************************************/
/* Client and Server Routes */
/*****************************************************************************/
// TODO: use these as per the Event Mind CLI tool.
//Router.configure({
// templateNameConverter: 'upperCamelCase',
// routeControllerNameConverter: 'upperCamelCase'
//});
Router.configure({
layoutTemplate: 'devLayout',
notFoundTemplate: 'devMain',
loadingTemplate: 'loading'
});
Router.onRun(function () {Session.set("waiting-on", null); });
Router.onBeforeAction(function() { Alerts.clearSeen(); });
var filters = {
nProgressHook: function (pause) {
// we're done waiting on all subs
if (this.ready()) {
NProgress.done();
} else {
NProgress.start();
pause(); // stop downstream funcs from running
}
}
};
Router.onBeforeAction(filters.nProgressHook);
Meteor.startup(function () {
Router.map(function () {
this.route('loading');
// reset password urls use hash fragments instead of url paths/query
// strings so that the reset password token is not sent over the wire
// on the http request
this.route('reset-password', {
template: 'devMain',
layoutTemplate: 'devLayout',
onRun: function () {
var token = this.params.hash;
Meteor.logout(function () {
Session.set("viewing-settings", true);
Session.set("set-password-token", token);
Session.set("settings-set-password", true);
// Session.set("enrolling", true) // do something special?
});
}
});
this.route('verify-email', {
template: 'devMain',
layoutTemplate: 'devLayout',
action: function () {
var self = this;
var token = self.params.hash;
Accounts.verifyEmail(token, function (err) {
if (!err) {
Alerts.throw({
message: "Your email address is now verified!",
type: "success", where: "main",
autoremove: 3000
});
Router.go('home');
} else {
Alerts.throw({
message: "Hmm, something went wrong: \""+err.reason +
"\". Try again?",
type: "danger", where: "main"
});
Session.set("viewing-settings", true);
Router.go('home');
}
});
}
});
this.route('leave-game', {
template: 'devMain',
layoutTemplate: 'devLayout',
action: function () {
var self = this;
var token = self.params.hash;
Meteor.call("leaveGameViaToken", token, function (err, res) {
if (!err) {
// Idempotently verify user's email,
// since they got the token via email.
Accounts.verifyEmail(token);
if (res.error) {
// e.g. "Leave-game link is for unknown game"
Alerts.throw({
message: res.error.reason, type: "danger", where: "main"
});
Router.go("home");
} else {
Alerts.throw({
message: "OK, you are no longer in this game.",
type: "success", where: res.gameId
});
Router.go("devDetail", {_id: res.gameId});
}
} else {
Alerts.throw({
message: "Hmm, something went wrong: \""+err.reason + "\".",
type: "danger", where: "main"
});
Router.go("home");
}
});
}
});
this.route('game-on', {
template: 'devMain',
layoutTemplate: 'devLayout',
action: function () {
var self = this;
var token = self.params.hash;
Meteor.call("gameOnViaToken", token, function (err, res) {
if (err || (res && res.error)) {
errorMessage = err ? "Hmm, something went wrong: \"" + err.reason + "\"." : res.error.reason;
Alerts.throw({
message: errorMessage, type: "danger", where: "main"
});
Router.go("home");
} else {
Alerts.throw({
message: "Woohoo! Players will be notified.",
type: "success", where: res.gameId
});
Router.go("devDetail", {_id: res.gameId, token: token });
}
});
}
});
this.route('cancel-game', {
template: 'devMain',
layoutTemplate: 'devLayout',
action: function () {
var self = this;
var token = self.params.hash;
Meteor.call("cancelGameViaToken", token, function (err, res) {
if (!err) {
Accounts.verifyEmail(token);
if (res.error) {
Alerts.throw({
message: res.error.reason, type: "danger", where: "main"
});
Router.go("home");
} else {
Alerts.throw({
message: "OK, your game is now cancelled, and players "
+ "will be notified.",
type: "success", where: "main"
});
Router.go("home");
}
} else {
Alerts.throw({
message: "Hmm, something went wrong: \""+err.reason + "\".",
type: "danger", where: "main"
});
Router.go("home");
}
});
}
});
// quite similar to 'leave-game' route
this.route('unsubscribe-all', {
template: 'devMain',
layoutTemplate: 'devLayout',
action: function () {
var self = this;
var token = self.params.hash;
Meteor.call("unsubscribeAllViaToken", token, function (err, res) {
if (!err) {
// Idempotently verify user's email,
// since they got the token via email.
Accounts.verifyEmail(token);
if (res.error) {
// e.g. "Token provided in link is not an unsubscribe-all token"
Alerts.throw({
message: res.error.reason, type: "danger", where: "main"
});
Router.go("home");
} else {
Alerts.throw({
message: "OK, you will no longer receive emails "
+ "from Push Pickup.",
type: "success", where: "main"
});
Router.go("home");
}
} else {
Alerts.throw({
message: "Hmm, something went wrong: \""+err.reason + "\".",
type: "danger", where: "main"
});
Router.go("home");
}
});
}
});
this.route('enroll-account', {
template: 'devMain',
layoutTemplate: 'devLayout',
onRun: function () {
var token = this.params.hash;
Meteor.logout(function () {
Session.set("viewing-settings", true);
Session.set("set-password-token", token);
Session.set("settings-set-password", true);
// Session.set("enrolling", true) // do something special?
});
}
});
// the home page. listing and searching for games
this.route('home', {
path: '/',
template: 'devMain',
layoutTemplate: 'devLayout'
});
// typical user interaction with a single game
this.route('devDetail', {
path: '/g/:_id/:token?',
layoutTemplate: 'devLayout',
onRun: function () {
Session.set("joined-game", null);
},
waitOn: function () {
return Meteor.subscribe('game', this.params._id);
},
onBeforeAction: function (pause) {
Session.set("soloGame", this.params._id);
},
data: function () {
var game = Games.findOne(this.params._id);
if (game) {
Session.set("gameExists", true);
}
return game;
},
action: function () {
var token = this.params.token;
if (Session.get("gameExists")) {
this.render();
} else {
Router.go('home');
Alerts.throw({
message: "Game not found",
type: "warning", where: "top"
});
}
if (token) {
Meteor.call("sendReminderEmailsViaToken", token, function (err, res) {
var errorMessage;
Accounts.verifyEmail(token);
if (err || (res && res.error)) {
errorMessage = err ? "Hmm, something went wrong: \"" + err.reason + "\"." : res.error.reason;
Alerts.throw({
message: errorMessage, type: "danger", where: "main"
});
Router.go("home");
}
});
}
},
onStop: function () {
Session.set("soloGame", null);
Session.set("gameExists", null);
}
});
this.route('devAddGame', {
path: '/addGame',
template: 'devEditableGame',
layoutTemplate: 'devLayout',
onRun: function () {
Session.set("selectedLocationPoint", null);
Session.set("newGameDay", null);
Session.set("newGameTime", null);
InviteList.remove({});
},
waitOn: function() {
Meteor.subscribe('recently-played');
},
data: function () {
return {
action: 'add',
title: 'Add game',
submit: 'Add game'
};
}
});
this.route('invitePreviousPlayers', {
path: 'invitePlayers',
template: 'invitePreviousPlayers',
layoutTemplate: 'devLayout'
});
this.route('devEditGame', {
path: '/editGame/:_id',
template: 'devEditableGame',
layoutTemplate: 'devLayout',
onRun: function () {
Session.set("selectedLocationPoint", null);
},
waitOn: function () {
return Meteor.subscribe('game', this.params._id);
},
onBeforeAction: function (pause) {
Session.set("soloGame", this.params._id);
},
data: function () {
return _.extend({
action: 'edit',
title: 'Edit game',
submit: 'Update game'
}, Games.findOne(this.params._id));
},
action: function () {
var self = this;
var user = Meteor.user();
var game = self.data();
if (user && user._id === game.creator.userId ||
user && user.admin) {
self.render();
} else {
Router.go('home');
}
}
});
this.route('adminView', {
path: '/admin',
onBeforeAction: function () {
var user = Meteor.user();
if (!user || !user.admin) {
this.render('home');
}
}
});
});
});
New onBeforeAction hook must call this.next(); You must call it in every onBeforeAction. For example your admin route in new Iron Router would look like this:
Router.route('/admin', {
name: 'adminView',
onBeforeAction: function () {
var user = Meteor.user();
if (!user || !user.admin) {
this.render('home');
}
this.next();
}
});
Replace all this.route(...) in Router.map with Router.route('/path', options) and remove Router.map()
Your global onBeforeAction will look like this:
Router.onBeforeAction(function() {
Alerts.clearSeen();
this.next();
});
Also, you don't need to wrap your routes in Meteor.startup(...). You can remove it.
And there is no pause parameter anymore, instead of pause call this.next() outside condition:
var filters = {
nProgressHook: function () {
// we're done waiting on all subs
if (this.ready()) {
NProgress.done();
} else {
NProgress.start();
}
this.next();
}
};
Router.onBeforeAction(filters.nProgressHook);

Meteor.js calling a template.helpers function vs global variable

I am using Reactive-table to display paginated data in my meteor.js app as shown below, yet data displayed in Reactive-table is dependent on on specific user event (Selecting client, project, date range and clicking on the submit button). So I was wondering if it is possible to trigger template.helpers >> myCollection function from the 'submit form' event? OR is it better to define a global variable to store data returned from user query based on the user (client, project, date range selection) then make this global variable the return from the myCollection function?
I have tried researching how to call .helpers function from an template.events event but couldn't find any information. So any help on which approach is better and if calling the .events function is better then how to do that, will be highly appreciated. Thanks.
Below is the code I have in my app:
Template.detailedreport.rendered = function() {
Session.set("dreport_customer", "");
Session.set("dreport_project", "");
Session.set("dreport_startDate", new Date());
Session.set("dreport_endDate", new Date());
$('.set-start-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-end-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-start-date').on("dp.change",function (e) {
Session.set("dreport_startDate", $('.set-start-date').data('DateTimePicker').getDate().toLocaleString());
});
$('.set-end-date').on("dp.change",function (e) {
Session.set("dreport_endDate", $('.set-end-date').data('DateTimePicker').getDate().toLocaleString());
});
};
Template.detailedreport.helpers({
customerslist: function() {
return Customers.find({}, {sort:{name: -1}});
},
projectslist: function() {
return Projects.find({customerid: Session.get("dreport_customer")}, {sort:{title: -1}});
},
myCollection: function () {
var now = Session.get("dreport_startDate");
var then = Session.get("dreport_endDate");
var custID = Session.get("dreport_customer");
var projID = Session.get("dreport_project");
Meteor.call('logSummary', now, then, projID, custID, function(error, data){
if(error)
return alert(error.reason);
return data;
});
}
},
settings: function () {
return {
rowsPerPage: 10,
showFilter: true,
showColumnToggles: false,
fields: [
{ key: '0._id.day', label: 'Day' },
{ key: '0.totalhours', label: 'Hours Spent'}
]
};
}
});
Template.detailedreport.events({
'submit form': function(e) {
e.preventDefault();
var now = $('.set-start-date').data('DateTimePicker').getDate().toLocaleString();
var then = $('.set-end-date').data('DateTimePicker').getDate().toLocaleString();
var custID = $(e.target).find('[name=customer]').val();
var projID = $(e.target).find('[name=project]').val();
//Here is the problem as I am not sure how to refresh myCollection function in .helpers
},
'change #customer': function(e){
Session.set("dreport_project", "");
Session.set("dreport_customer", e.currentTarget.value);
},
'change #project': function(e){
Session.set("dreport_project", e.currentTarget.value);
}
});
Template:
<div>
{{> reactiveTable class="table table-bordered table-hover" collection=myCollection settings=settings}}
</div>
Server:
Meteor.methods({
logSummary: function(startDate, endDate, projid, custid){
//Left without filtering based on date, proj, cust for testing only...
return Storylog.find({});
}
});
Template helpers are reactive, meaning that they will be recomputed if their dependencies change. So all you need to do is update their dependencies and then the myCollection helper will be recomputed.
Replace your comment // Here is the problem... with:
Session.set('dreport_endDate', then);
Session.set('dreport_startDate', now);
Session.set('dreport_project', projID);
Session.set('dreport_customer', custID);

Resources