Most of my template helpers result in blaze errors and I'm not sure why. What makes them more strange is that they do not block rendering or events from the templates at all, in fact, the app works fine.
The main issue is a messy, messy, console. An example of this is below:
Template.templatename.helpers({
adminhelper: function(){
var theUser = Meteor.user(),
theUserId = theUser['_id'];
if(theUserId == "XXX"){
return true;
}
}
});
Just one way of checking which user is an admin user. This results in:
Exception in template helper: TypeError: Cannot read property '_id' of undefined
at Object.Template.templatename.helpers.adminhelper (http://localhost:3000/client/lib/helpers.js?37db222f849959237e4f36abdd8eba8f4157bd32:5:23)
at http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:2693:16
at http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:1602:16
at Object.Spacebars.call (http://localhost:3000/packages/spacebars.js?3c496d2950151d744a8574297b46d2763a123bdf:169:18)
at Template.manage.Blaze.If.HTML.HEADER.HTML.DIV.class (http://localhost:3000/client/views/template.templatename.js?868248757c652b031f64adad0edec9e2a276b925:6:22)
at null.<anonymous> (http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:2454:44)
at http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:1795:16
at Object.Blaze._withCurrentView (http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:2029:12)
at viewAutorun (http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:1794:18)
at Tracker.Computation._compute (http://localhost:3000/packages/tracker.js?192a05cc46b867dadbe8bf90dd961f6f8fd1574f:288:36)
Interestingly, client/views/template.templatename.js does not exist. I put all helpers in a helpers.js file, and all events in an events.js file.
For my route I have
Router.route('/theurl',function(){
this.render('templatename');
},{
waitOn: function(){
return Meteor.user();
}
});
What can I do to avoid these issues in the future?
Just use a guard to check for the existence of Meteor.user() before extracting the _id. Waiting on Meteor.user() in the route doesn't work, as waitOn requires a subscription. Alternatively you can just do this:
Template.templatename.helpers({
adminhelper: function() {
return Meteor.userId() === 'XXX';
}
});
An even better solution is to use the roles package.
Related
I have a Meteor project where I'm using audit-argument-check. I'm getting an error message
Error: Did not check() all arguments during publisher 'document
I'm know this is related the audit-argument-check not being able to check all arguments. But as far as I'm concerned, I checked all of them. Concrete, I have defined a collection 'documents' and attached a SimpleSchema. As part of iron-router, I have the following:
Router.route('customerDocumentShow', {
template: 'customerDocumentShow',
path: 'customer/documents/:_id',
waitOn: function () {
return Meteor.subscribe('document', this.params._id);
},
data: function () {
return Documents.findOne(this.params._id);
}
});
So I'm passing only the documentId (this.params._id). On the server, I have defined a method:
Meteor.methods({
documentsReadMethod: function(documentId){
check(documentId, String);
var documentItem = Document.findOne(argument);
if (!documentItem) {
throw new Meteor.Error(500, 'Error 500: Not Found', 'No documents found.');
}
return documentItem;
}
});
So I'm checking to documentId in the server method. So not sure why I'm getting this error message.
Note: One thing I'm not entirely sure about though is how I need to call this method (right now, it's documentsReadMethod_. I'm not explicitly calling (on the client):
Meteor.call(documentsReadMethod, this.params_id);
as I'm using autoform, collection2 and simpleschema. I've been spending the entire weekend, but have no clue. Any idea's ?
Note: the code is on github: https://github.com/wymedia/meteor-starter-base
The problem is in the publish. You didn't check the id here:
https://github.com/wymedia/meteor-starter-base/blob/master/server/publications/documents.js#L16
Just add check(id, String); line 16 and it should work.
I have the same problem with another tuto !
Answer found at check is not defined in meteor.js : since Meteor v1.2, you have to add this package:
$ meteor add check
According to this article here:
https://dweldon.silvrback.com/common-mistakes
Subscriptions don't block
Many aspects of the framework seem like magic. So much so that it may
cause you to forget how web browsers work. Take this simple example:
Meteor.subscribe('posts');
var post = Posts.findOne();
The idea that post will be undefined is the root cause of roughly one
in twenty meteor questions on stackoverflow.
So then why doesn't subscribe have a callback, and if it does, why isn't it referenced more often by the Meteor literati?
Why don't we have:
Meteor.subscribe('posts', function(err, posts){
//when the items/posts actually arrive
});
I hope my question makes sense.
Maybe I don't get the question, but the Meteor.Subscribe function has callbacks named onError and onReady methods.
Optional. May include onError and onReady callbacks. If a function is
passed instead of an object, it is interpreted as an onReady callback.
From docs.
For example.
Meteor.subscribe("posts", {
onReady: function () { console.log("onReady And the Items actually Arrive", arguments); },
onError: function () { console.log("onError", arguments); }
});
Also check this GitHub issue
Note: I have updated the article after reading this question.
While subscribe does have an optional callback, I intentionally avoided it in the original article because there aren't currently any common patterns that use it. In other words, I didn't want readers to come away from the article thinking that callbacks were actually the right solution to this problem.
In production applications, subscriptions typically come in two flavors:
Global: initiated as soon as the client starts, or maybe in an autorun.
Route: initiated as via a subscriptions or waitOn option.
It's also worth noting that in recent weeks, the template subscription pattern has emerged, though it hasn't seen wide adoption yet.
In all of these cases, the subscription is started and then can either be asynchronously checked for a reactive ready state, or ignored with the use of guards to prevent reference errors.
Because ready is reactive, this effectively gives us the same benefits of a callback, but with fewer lines of code. Let's look at two examples:
example 1
Meteor.subscribe('posts', function() {
Session.set('postsReady', true);
});
Tracker.autorun(function() {
if (Session.get('postsReady'))
showFancyAnimation();
});
example 2
var handle = Meteor.subscribe('posts');
Tracker.autorun(function() {
if (handle.ready())
showFancyAnimation();
});
Both examples demonstrate the same concept - subscribing and then reactively testing the state of the subscription. As you can see there really isn't a benefit to the callback.
Finally, (as I now point out in the article), subscriptions are often spatially separated from the code which uses them. You'll typically subscribe in your route code and consume the results in your templates. For this reason you almost never see code which looks like:
Meteor.subscribe('posts', function() {
showFancyAnimation();
});
In fact, the only place I ever encounter code like the above is in SO answers because the author is trying to make a quick demonstration rather than trying to show a typical usage pattern.
Meteor.subscribe has been enhanced since v1.2. One of its callbacks onError is now replaced with onStop in Meteor v1.2.0.2 documentation
callbacks Function or Object
Optional. May include onStop and onReady callbacks. If there is an
error, it is passed as an argument to onStop. If a function is passed
instead of an object, it is interpreted as an onReady callback.
Having that enhancement, Meteor.subscribe is used with callbacks as an object
Meteor.subscribe( 'collection', {
onStop: function( error /* optional */ ) {
// when the sub terminates for any reason,
// with an error argument if an error triggered the stop
},
onReady: function() {
// when ready
}
});
However, onError still works for backward compatibility. Be aware that some popular packages, such as SubsManager still uses onError. That being said such snippet below is now deprecated but doesn't break .
Meteor.subscribe( 'collection', {
onError: function( error ) {
// if the subscribe terminates with an error
},
onReady: function() {
// when ready
}
});
On the other hand, Meteor.subscribe can be used with a callback as a function as before
Meteor.subscribe( 'collection', function() {
// when ready
});
As my personal notice, if Meteor.subscribe happens to be passed with careless multiple callback functions, only the last one takes effect as the onReady callback.
Meteor.subscribe( 'collection', function() {
// this doesn't execute.
}, function() {
// when ready.
});
The associated Git commitment is listed here for a reference.
Why is it that .findOne() does not work when executed inside the Router?
It always returns undefined.
Yet .find() works without any problems. Also tested .findOne() by manually entering the condition and the ._id manually.
Is .findOne() not designed to work inside the Router?
It's working properly in my application. I implemented it like this:
Router.map(function() {
this.route('training', {
path: '/training/:id',
data: function() {
return Trainings.findOne({id: this.params.id});
},
notFoundTemplate: 'notFound',
title: "Training"
});
});
it works fine!
maybe you're trying to pass the result to an iterator? it's not a cursor.
try a find().fetch() and use the result in the same way.
if it's a data/timing issue you could also guard with a ready() function.
you don't need to if you're using it reactively but this gives you a bit more explicit knowhow when things happen, yet without using a waitOn.
in coffeescript:
#---------routes ---------
#route 'routeName',
path: '/path/to/:cname'
onBeforeAction: ->
Meteor.subscribe('Things', {
cname: #params.cname
})
this.next()
data: ->
if #ready()
data = {
params: #params
}
data.lesson = Things.findOne({cname: #params.cname})
return data
Short version:
Session.get(), and Template.currentData() that are supposed to be passed to the template by the Router often turn out to be undefined in Template.<templateName>.render callback. This happens quite randomly, most often on hot code pushes, but not always, and not only on hot code pushes.
Longer version:
I'm using Iron Router to pass data context to the template appBody:
Router.route('/:_mapid', function() {
if (!isNaN(this.params._mapid)) {
Session.set('currentMap',Maps.findOne({mapid: Number(this.params._mapid)}));
this.render('appBody', {
data: function() { return Session.get("currentMap") }
});
}
});
The template then uses d3 to generate a bunch of div's, and set the dynamic page title in Template.appBody.rendered callback:
Template.appBody.rendered = function() {
Deps.autorun(function() {
d3.select("#map_body").selectAll("div").remove();
d3.select("#map_body").selectAll("div")
.data(Nodes.find({mapid: Template.currentData().mapid }).fetch(), function(d) {return d.nodeparentid;})
.enter().append("div")
.attr("id", function(d){return "node"+ d.nodeparentid})
.style("position","absolute")
.style("top",function(d) {return d.toppos+"px"})
.style("left",function(d) {return d.leftpos+"px"})
.style("width",function(d) {return d.width+"px"})
.style("height",function(d) {return d.height+"px"})
.classed("node", true)
document.title = "Map - " + Session.get("currentMap").title;
}
As you can see I try to pass the data context to the template in two different ways: by setting a global currentMap object via Session.set and by passing data key via the Router, and then accessing it via Template.currentData() method.
For some reason both of these method often fail (although I still can't figure out under what conditions). Here is a sample error I get from the browser console when trying to set a document title:
Exception from Tracker afterFlush function: Cannot read property 'title' of undefined
TypeError: Cannot read property 'title' of undefined
at http://localhost:3000/client/templates/app_body.js?47b256634607ca16879aa0ed823593aec01ee840:122:31
at Tracker.Computation._compute (http://localhost:3000/packages/tracker.js?192a05cc46b867dadbe8bf90dd961f6f8fd1574f:288:36)
at new Tracker.Computation (http://localhost:3000/packages/tracker.js?192a05cc46b867dadbe8bf90dd961f6f8fd1574f:206:10)
at Object.Tracker.autorun (http://localhost:3000/packages/tracker.js?192a05cc46b867dadbe8bf90dd961f6f8fd1574f:476:11)
at Template.appBody.rendered (http://localhost:3000/client/templates/app_body.js?47b256634607ca16879aa0ed823593aec01ee840:117:10)
at null.<anonymous> (http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:2970:21)
at http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:1720:14
at Object.Blaze._withCurrentView (http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:2029:12)
at http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:1719:15
at Tracker.flush (http://localhost:3000/packages/tracker.js?192a05cc46b867dadbe8bf90dd961f6f8fd1574f:438:11)
Any ideas on what could be causing this?
In Iron router path controller I'm trying to load different templates depending on user login status like this:
Router.map(function() {
this.route('start', {
path: '/',
controller: StartController
});
})
StartController = RouteController.extend({
template: Meteor.user() ? 'home' : 'landing',
waitOn: function() {
return [Meteor.subscribe('something')]
}
});
However I get exception in console:
"...Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions..."
I don't really understand what that error means, and what Am I doing wrong here.
The code you've posted is executed on the server. On the server, Meteor.user() can only be executed in methods.
However, even if this error wouldn't exist, your code wouldn't function as you wish anyway. Meteor.user() ? 'home' : 'landing' will be executed once in the beginning (when the user probably isn't logged in), and won't be re-executed when the user signs in/out.
Read this in the docs, which is kind of doing what you want.