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;
}
});
Related
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'
I am able to get the aggreate values from server to client, but could not display it on the template. Am i missing something here.Appreciate your help.Iam a newbie in meteor.
//client side javascript
Template.DashboardCategoriesChart.helpers({
'CategoryAggregateItem':function(){
var res;
Meteor.call("getAggregateCategoriesSum",function(errors,results){
console.log("results value: "+ JSON.stringify(results))
return results ;
};
};
});
//stringfy value returned
results value: [
{"_id":"Household","totalAmount":420},
{"_id":"Insurance","totalAmount":235},
{"_id":"Family","totalAmount":1358},
{"_id":"Utilities","totalAmount":5371.5},
{"_id":"Automobile","totalAmount":500},
{"_id":"Home Office","totalAmount":290},
{"_id":"Travel","totalAmount":14},
{"_id":"Food","totalAmount":303}
]
//Template
{{#each CategoryAggregateItem}}
<tr>
<td>{{_id}}</td><td>{{totalAmount}}</td>
</tr>
{{/each}}
The following code worked, thanks a ton JeremyK for leading to right direction.
Template.DashboardCategoriesChart.helpers({
'CategoryAggregateItem':function(){
return Template.instance().CategoryAggregateItem.get(); //this.CategoryAggregateItem;
}
});
Template.DashboardCategoriesChart.onCreated(function () {
var instance = this;
this.CategoryAggregateItem = new ReactiveVar(null);
Meteor.call("getAggregateCategoriesSum",function(errors,results){
console.log("results value: "+ JSON.stringify(results))
instance.CategoryAggregateItem.set(results);
})
})
Try changing your client side javascript to this:
Template.DashboardCategoriesChart.onCreated(function () {
_this = this;
_this.CategoryAggregateItem = new ReactiveVar(null);
Meteor.call("getAggregateCategoriesSum",function(errors,results){
console.log("results value: "+ JSON.stringify(results))
_this.CategoryAggregateItem.set(results);
}
});
Template.DashboardCategoriesChart.helpers({
'CategoryAggregateItem':function(){
return Template.instance().CategoryAggregateItem.get();
});
When the Callback from Meteor.Call is triggered, it will update the ReactiveVar. This causes the template to rerender and display the retrieved data.
You may also want to provide alternative markup in your template for when the helper returns null, before the data is received by the client.
I have removed autopublish and now have simple publish and subscribe for starters.
Meteor.publish("records", function() {
return Records.find({});
});
and
Meteor.subscribe('records');
In Mongol I can see my nested data items, which is a geoJSON object. However, when I try to access the item with here it doesn't work, unless autopublish is on...
Template.recordView.rendered = function() {
var geoData = Template.currentData().loc;
};
I have tried just "loc" and parentData().loc. None of them are defined. What has autopublish removed that I have not put back?
Where are you subscribing to your data? I recommend that you delegate that for your template.
Template.recordView.onCreated(function() {
var self = this;
self.autorun(function() {
// Do reactive stuff here
Meteor.subscribe("records");
});
});
Template.recordView.helpers({
// Data is now available here
'geoData': function() {
return Records.find().loc;
}
});
Now you have access to your data template-level. Do whatever you want with it and return a helper. In your .html:
<template name="recordView">
...
{{#if Template.subscriptionsReady}}
{{geoData}}
{{else}}
Loading...
{{/if}}
...
</template>
You'll wait for all your data to arrive before rendering the content you provided in your helper.
In Iron-router, we can pass the data to a page in the data field. For example:
Router.map(function () {
this.route('myroute', {
path: '/route',
template: 'myTemplate',
data: function () {
return {
title: getTitle(),
description: getDescription(),
}
}
});
});
In the template, title and description are actually some value passed to subtemplates:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
Since the data field in the iron-router is reactive, whenever a session variable change, the data field is recalculated.
In this case, however, the session variable in getTitle function only changes the template "titleTemplate", and the session variable in getDescription() function only changes the template "descriptionTemplate".
If the session variable in the getTitle() function changes, I would like to only execute the getTitle() function, and do not execute the getDescription() function. If possible, I would also like to only render the "titleTemplate" and do not render "descriptionTemplate".
I wonder whether that is possible. If this is not the right way of writing the Meteor application, what is a better way to do it?
Thanks.
This is an interesting situation. Despite the fact that the getTitle and getDescription functions may be dependent on completely different reactive variables, they will both be recomputed whenever either one of them changes.
One possible solution is to pass the functions themselves instead of the result of calling the functions. That may or may not be convenient depending on how they are used in the sub-templates, but it will prevent them from both being run every time. Here is a simple example:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
<template name="titleTemplate">
<p>{{excitedTitle}}</p>
</template>
<template name="descriptionTemplate">
<p>{{this}}</p>
</template>
var getTitle = function() {
console.log('computed title');
return Session.get('title');
};
var getDescription = function() {
console.log('computed description');
return Session.get('description');
};
Router.map(function() {
this.route('home', {
path: '/',
template: 'myTemplate',
data: function() {
return {
title: getTitle,
description: getDescription
};
}
});
});
Meteor.startup(function() {
Session.setDefault('title', 'title1');
Session.setDefault('description', 'description1');
});
Template.titleTemplate.excitedTitle = function() {
return "" + (this.toUpperCase()) + "!";
};
From the console you can change the session variables (title and description) and you will see that only one function will be run at a time. I hope that helps.
One way to solve this is to not use the data context, but just use template specific helpers. Since I don't know what your getTitle and getDescription function do, I can't tell whether that is an option for you. It depends on whether you need to use the this object in those functions and need this to refer to the route object or not. If not, then the following seems like the better solution:
JS:
Router.map(function () {
this.route('myroute', {
path: '/route',
template: 'myTemplate'
});
});
Template.myTemplate.title = getTitle;
Template.myTemplate.description = getDescription;
HTML:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
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