Publish/subscribe not loading in Blaze template - meteor

My code was working fine until I implemented publish/subscribe. I followed the basic tutorial and checked the source code and I'm not doing anything different. Everything builds and runs but nothing from MongoDB gets displayed in the Blaze template.
imports/api/features.js
if (Meteor.isServer) {
Meteor.publish('features', function featuresPublication() {
return Features.find({});
});
Meteor.publish('comments', function commentsPublication() {
return Features.find({}, {fields: {comments: 0}});
})
};
client/main.js
Template.body.onCreated(function bodyOnCreated() {
Meteor.subscribe('features');
});
client/main.html
<body>
<h1 id="title">Feature Requests</h1>
{{#if currentUser}}
<button class="ui green button create" id="create">Add a New Feature Request</button>
{{> requestForm}}
{{#each features}}
{{> feature}}
{{/each}}
{{else}}
{{> loginButtons}}
{{/if}}
</body>
Edit #1
Before I ran meteor remove autopublish my code looked like this and worked:
Template.body.helpers({
features() {
return Features.find({}, {sort: {createdAt: -1}});
},
comments() {
return Features.find({}, {fields: {comments: 0}});
},
});
Edit #2
Thanks to everyone who contributed an answer. I fundamentally misunderstood how publish/subscribe worked. I didn't realize I still needed to call return Features.find({}) after I subscribed. Here's my working code:
import { Features } from '../imports/api/features.js';
import '../imports/api/features.js'
Template.body.onCreated(function bodyOnCreated() {
Meteor.subscribe('features');
});
Template.body.helpers({
features: function() {
return Features.find({});
}
});

Disregard the first answer. The lack of an autorun is what first caught my attention but since you're not passing any args to subscribe it is not needed.
My next question would be: In client/main.html, where is the reference to features coming from? Is there a features helper on Template.body? If not, you'll need to add it:
Template.body.helpers({
features: function() {
return Features.find({});
}
});
Also, see Meteor Subscribe & Publish with external api

Try this:
Template.body.onCreated(function() {
const self = this;
self.autorun(() => {
self.subscribe('features');
});
});
Also, see https://guide.meteor.com/data-loading.html#organizing-subscriptions.

I see you are using the imports directory. Have you remembered to import your publication file to the server/main.js file?
server/main:
import 'imports/path/to/publications.js'

Related

Meteor - publish and subscribe issue

I am new to Meteor and trying to get publish and subscribe to work. Here is my code for subscribe:
//isClient
Session.set("userSetLimit",10);
Template.MoodList.helpers({
hlpinvoices: function(){
var curinvoices = Meteor.subscribe('invoices', Meteor.userId(),Session.get("userSetLimit"));
return curinvoices;
}
});
publish:
//isServer
Meteor.publish('invoices',function (creator,limit) {
return Invoices.find({CreatedBy:creator},{sort:{DateCreated:-1}, limit:limit});
})
and template:
<template name="MoodList">
<ul>
{{#each hlpinvoices}}
{{> invoice}}
{{/each}}
</ul>
</template>
And this is the error I got:
Exception in defer callback: Error: {{#each}} currently only accepts
arrays, cursors or falsey values.
But if I use do:
//isClient
Session.set("userSetLimit",10);
Template.MoodList.helpers({
hlpinvoices: function(){
return Invoices.find({CreatedBy:Meteor.userId()},{sort:{DateCreated:-1}, limit:Session.get("userSetLimit")});
}
});
I don't have problem. Any idea how to resolve this?
subscribe is something you call in order to get data from your server, it doesn't actually return any data itself - hence the error. You'll want to call subscribe in the onCreated method of your template:
Template.MoodList.onCreated( function() {
Meteor.subscribe('invoices', Meteor.userId(),Session.get("userSetLimit"));
});
Then in your helper you can return the invoices:
Template.MoodList.helpers({
hlpinvoices: function(){
return Invoices.find({CreatedBy:Meteor.userId()},{sort:{DateCreated:-1}, limit:Session.get("userSetLimit")});
}
});
You can learn more about subscribing to data in the Meteor Guide. Hope that helps!
Meteor.subscribe gives you subscription Id only .Try this
Template.MoodList.helpers({
hlpinvoices: function(){
Meteor.subscribe('invoices', Meteor.userId(),Session.get("userSetLimit"));
var curunnvoices=Invoices.find({CreatedBy:Meteor.userId()},{sort:{DateCreated:-1}, limit:Session.get("userSetLimit")})
return curinvoices;
}
});

With Iron Router, Find() and FindOne()

I loaded Iron:Router with my Meteor, and now I'm having difficulties loading both all of a collection, and one specific entry.
What I'm trying to do: In the Nav Bar, I want to list all of a user's previous entries. I got that working great. It lists each into a drop down, provides a proper link, and loads up only what the user has previous input. In the body it's suppose to load up specific information for each entry.
Whats not working: It's either not giving me a findOne() where the params._id equal the id in the route, or it's not loading anything. The location is correct. It's just not loading the info like it should.
UPDATE: I moved some things around and got the 'name' field to print out, still not able to get it to verify owner just yet. Console prints out: "Exception in template helper: ReferenceError: currentCharacter is not defined" Replacing with current code.
What am I doing wrong? Below is the code:
Route:
this.route('character/:_id', {
template: 'character',
subscriptions: function() {
return Meteor.subscribe("characterlist");
return Meteor.subscribe("characterlist", this.params._id);
},
data: function () {
var routeid = this.params._id;
return {
currentCharacter: CharList.findOne({_id: routeid}),
characterlist: CharList.find({'owner':Meteor.userId()})
};
}
});
Template Helper Class:
Template.character.helpers({
characterlist: function () {
return CharList.find({}, {sort: {createdAt: -1}});
},
currentCharacter: function () {
return CharList.findOne({'_id':Router.current().params._id});
},
isOwner: function () {
return currentCharacter.owner === Meteor.userId();
}
});
HTML:
<template name='character'>
<div class="container-body">
{{#if currentUser}}
<div class="well">
{{currentCharacter.name}}
{{#with currentCharacter}}
{{#if isOwner}}
<p>Character: {{name}}</p>
{{else}}
<p>You are not approved to make spends for this character.</p>
{{/if}}
{{/with}}
</div>
{{else}}
<h4>Please log in.</h4>
{{/if}}
</div>
</template>
Let me sigh metaphorically out loud online. Like usual when I ask for help I find what i did wrong.
It needs to get "this" object and pull the "owner" type out. Once it has "this.owner" it can verify the user is of course the correct owner. I was trying to be smart and didn't realize it was asking for the wrong information for four hours! Code below:
isOwner: function () {
return this.owner === Meteor.userId();
}

Defined two meteor local collections and helpers exactly the same. One helper works. The other doesn't

I create these two local collections (the code is actually written one after the other exactly like below):
ShoppingCartCollection = new Meteor.Collection(null);
CurrentPricesCollection = new Meteor.Collection(null);
Inside Template.myTemplate.rendered I add some initial info into these collections (again, code is one after the other):
ShoppingCartCollection.insert({"sqft" : "not yet entered"});
CurrentPricesCollection.insert({"hdrPhotos" : 100});
I've got these two global helpers in helpers.js (defined one after the other)
Handlebars.registerHelper("shoppingCart", function() {
return ShoppingCartCollection.findOne();
});
Handlebars.registerHelper("currentPrice", function() {
return CurrentPricesCollection.findOne();
});
When I load the page I immediately run these commands in the console:
> ShoppingCartCollection.findOne();
Object {sqft: "not yet entered", _id: "xcNmqJvMqqD5j7wwn"}
> CurrentPricesCollection.findOne();
Object {hdrPhotos: 100, _id: "LP38E3MZgzuYjvSec"}
In my template I use these helpers, but...
{{currentPrice.hdrPhotos}} //displays nothing
{{shoppingCart.sqft}} //displays "not yet entered"
How... what... ? How can this be? Are there some kind of gotchas that I could be missing? Some kind of dependency or load order that I'm not aware of?
The code you posted is working fine here.
Suggest comparing this code to the exact details of what you are doing. Also, look
for other problems, typos, etc.
Below is the exact test procedure I used:
From nothing, at the linux console:
meteor create sodebug
Note that this will produce files for a "hello world" type program.
Check the version:
meteor --version
Release 0.8.1.1
Edit sodebug/sodebug.js:
if (Meteor.isClient) {
// code autogenerated by meteor create
Template.hello.greeting = function () {
return "Welcome to sodebug.";
};
Template.hello.events({
'click input': function () {
// template data, if any, is available in 'this'
if (typeof console !== 'undefined')
console.log("You pressed the button");
}
});
// add your code here
ShoppingCartCollection = new Meteor.Collection(null);
CurrentPricesCollection = new Meteor.Collection(null);
ShoppingCartCollection.insert({"sqft" : "not yet entered"});
CurrentPricesCollection.insert({"hdrPhotos" : 100});
Handlebars.registerHelper("shoppingCart", function() {
return ShoppingCartCollection.findOne();
});
Handlebars.registerHelper("currentPrice", function() {
return CurrentPricesCollection.findOne();
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
Edit sodebug.html:
<head>
<title>sodebug</title>
</head>
<body>
{{> hello}}
{{> T1 }}
{{> T2 }}
</body>
<template name="T1">
<p>
{{shoppingCart.sqft}}
</p>
</template>
<template name="T2">
<p>
{{currentPrice.hdrPhotos}}
</p>
</template>
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<input type="button" value="Click" />
</template>
Run: meteor run
Manual tests:
Fire up chromium browser at localhost:3000
Check web browser console for collections data. PASS
Check web browser screen for templates data. PASS
Reorder templates in sodebug.html file, check web browser screen. PASS

Meteor template function not rendering

I am trying to make a template render something on the client; I think I tried everything possible (apart from the correct thing apparently).
Html:
<head>
<title>Groups</title>
</head>
<body>
{{loginButtons}}
{{>TplGroups}}
</body>
<template name="TplGroups">
groups found: {{ GroupCount }}
{{#each GetAllGroups}}
<div> hello, {{name}} group! </div>
{{/each}}
</template>
serverStartup.js:
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Meteor.publish("GroupCount"), function()
{
return Groups.find({});
}
});
}
and the Groups.js collection which exposes the two methods GroupCount and GetAllGroups, which I want to access on client side:
var Groups = new Meteor.Collection("groups");
Groups.insert({name: "John"});
if(Meteor.is_client)
{
Meteor.subscribe("GetAllGroups");
Meteor.subscribe("GroupCount");
Template.TplGroup.GetAllGroups = function()
{
return Groups.find({});
}
Template.TplGroup.GroupCount = function()
{
return Groups.find().count();
}
}
I have removed "insecure" and "autopublish" packages.
Where is my mistake? The two functions won't show on client.
Also what is the difference between declaring the functions as "publish" or declaring them as Template functions?
In browser console I get this:
event.returnValue is deprecated. Please use the standard event.preventDefault() instead. (jquery.js)
The publish method should look more or less like this
Meteor.publish("GetAllGroups", function () {
return Groups.find({});
});
#apendua pointed to the right solution. I took your code and refactored it to make the solution a little clearer:
server.js:
if (Meteor.isServer) {
// Publish groups
Meteor.publish('groups', function() {
return Groups.find();
});
}
groups.js
Groups = new Meteor.Collection('groups');
Groups.insert({name: 'John'});
if (Meteor.isClient) {
// Subscribe to groups
Meteor.subscribe('groups');
Template.TplGroup.GetAllGroups = function() {
return Groups.find();
}
Template.TplGroup.GroupCount = function() {
return Groups.find().count();
}
}
It is enough to publish just groups. In your groups.js you try to subscribe to a publication that does not exist (GetAllGroups). Better to just publish and subscribe to simply 'groups' and return the groups count as described above. Also with a newer version of meteor you should use Meteor.isClient and not Meteor.is_client.
The jQuery error you described is not related to your code and appears (at least what I think) because of some issue with Meteor and/or jQuery itself. Don't worry about that.
oups you just forgot "s" in your template name in your js file :
<template name="TplGroups"> <!-- what you wrote -->
and in your js you wrote :
Template.TplGroup.xxx
instead of :
Template.TplGroups.xxx

dynamically inserting templates in meteor

Ok so I've got my template in its own file named myApp.html. My template code is as follows
<template name="initialInsertion">
<div class="greeting">Hello there, {{first}} {{last}}!</div>
</template>
Now I want to insert this template into the DOM upon clicking a button. I've got my button rendered in the DOM and I have a click event tied to it as follows
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
Meteor.ui.render(function () {
$("body").append(Template.initialInsertion({first: "Alyssa", last: "Hacker"}));
})
}
}
Now obviously the $("body").append part is wrong but returning Template.initialInsertion... doesn't insert that template into the DOM. I've tried putting a partia {{> initialInsertion}}but that just errors out because I dont have first and last set yet... any clues?
Thanks guys
In meteor 1.x
'click .zaButton':function(){
Blaze.renderWithData(Template.someTemplate, {my: "data"}, $("#parrent-node")[0])
}
In meteor 0.8.3
'click .zaButton':function(){
var t = UI.renderWithData(Template.someTemplate, {my: "data"})
UI.insert(t, $(".some-parrent-to-append"))
}
Is first and last going into a Meteor.Collection eventually?
If not, the simplest way I know is to put the data into the session:
Template.chooseWhatToDo.events = {
'click .zaButton' : function () {
Session.set('first', 'Alyssa');
Session.set('last', 'Hacker');
}
}
Then you would define:
Template.initialInsertion.first = function () {
return Session.get('first');
}
Template.initialInsertion.last = function () {
return Session.get('last');
}
Template.initialInsertion.has_name = function () {
return Template.initialInsertion.first() && Template.initialInsertion.last();
}
Finally, adjust your .html template like this:
<template name="initialInsertion">
{{#if has_name}}
<div class="greeting">Hello there, {{first}} {{last}}!</div>
{{/if}}
</template>
This is the exact opposite solution to your question, but it seems like the "Meteor way". (Basically, don't worry about manipulating the DOM yourself, just embrace the sessions, collections and template system.) BTW, I'm still new with Meteor, so if this is not the "Meteor way", someone please let me know :-)
I think you may want to use Meteor.render within your append statement. Also, note that if you are passing data into your Template, then you must wrap Template.initialInsertion in an anonymous function, since that's what Meteor.render expects. I'm doing something similar that seems to be working:
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
$("body").append(Meteor.render(function() {
return Template.initialInsertion({first: "Alyssa", last: "Hacker"})
}));
}
}
Hope this helps!
Many answer here are going to have problems with the new Blaze engine. Here is a pattern that works in Meteor 0.8.0 with Blaze.
//HTML
<body>
{{>mainTemplate}}
</body>
//JS Client Initially
var current = Template.initialTemplate;
var currentDep = new Deps.Dependency;
Template.mainTemplate = function()
{
currentDep.depend();
return current;
};
function setTemplate( newTemplate )
{
current = newTemplate;
currentDep.changed();
};
//Later
setTemplate( Template.someOtherTemplate );
More info in this seccion of Meteor docs

Resources