In a meteor messenger app I have pages for each user you're talking to. But when you switch users it doesn't always switch the conversations.
It seems that the helper variable isn't refreshing. Any ideas?
Router.route('/cc/:name',{
template: "cc",
name: "cc",
data: function(){
var x = //...
chat = Chats.find({users: {$all: [x, Meteor.userId()]}});
}
});
helper
Template.cc.helpers({
'chat': function(){
return chat
}
});
template
<template name="cc">
{{#each chat}}
{{#each message}}
<span>{{senderName}}</span>
<b>{{content}}</b>
{{/each}}
</div>
{{/each}}
</template>
It's not reactive, becouse assigning is not an reactive operation. Helpers have reactive context, Chats.find is an reactive data source, but you have to read from reactive data source in reactive context to make it reactive.
Solution A:
It looks like an IronRouter, so pass data directly from Router. Read more here.
Solution B:
Turn chat into ReactiveVar. Read more here.
Related
Sorry kind of new to the Meteor framework!
I Subscribed to two Publish functions. Even if both publish functions target the same Collection, they both have different functions, that I would like to display in one template. How do I achieve this. I have done allot of research but there doesn't seem to be sufficient information on how to achieve.
Following are the two publish functions in code that I subscribe to:
.server/main.js:
Meteor.publish('MerchantTrending', function (categoryMan){
var currentUser = this.userId;
return buyList.find({ who:"Merchant", ownerId:currentUser, itemCategory: { $in: categoryMan } }, {skip: 0, limit: 3});
});
.server/main.js:
Meteor.publish('myTopViews', function (){
var currentUser = this.userId;
return buyList.find({ newArrivalsExpiryDate : {'$lte': new Date()}}, {ownerId:currentUser }, {skip: 0, limit: 3});
});
Following is the subscription function in code
.client/main.js:
Router.route('/MerchantLandingPage', {
subscriptions: function(){
var categoryMan = Session.get('category');
return Meteor.subscribe('MerchantTrending', categoryMan, 'merchantTopViews')
}
});
Now the helper function in code:
Template.MerchantLandingPage.helpers({
'top3Trending' : function () {
return buyList.find({}).fetch();
},
'myTopViews' : function () {
return buyList.find({}).fetch();
}
});
And now the template in code:
<template name="MerchantLandingPage">
##### *** Top three trending items *** ########
{{#each top3Trending}}
ItemName:: <b>{{itemName}}</b> <br>
Item Category:: <b>{{itemCategory}}</b> <br>
Discription:: <b>{{descriptions}}</b> <br>
Image:: {{this.photo._id}} <br>
Date Created:: {{createdDate}} <br>
{{/each}}
<br><br>
############ *** My top Views *** #############
{{#each myTopViews}}
ItemName:: <b>{{itemName}}</b> <br>
Item Category:: <b>{{itemCategory}}</b> <br>
Discription:: <b>{{descriptions}}</b> <br>
Image:: {{this.photo._id}} <br>
Date Created:: {{createdDate}} <br>
{{/each}}
</template>
Both {{#each myTopViews}} and {{#each top3Trending}} successfully display but not correctly. When the variable categoryMan in
Meteor.subscribe('MerchantTrending', categoryMan, 'merchantTopViews')
changes value, it affects both both the outcome of both {{#each myTopViews}} and {{#each top3Trending}}, when its only supposed to affect {{#each top3Trending}}.
How can I get the subscriptions to NOT have an affect on both {{#each myTopViews}} and {{#each top3Trending}}, but only {{#each myTopViews}} in my template?
Thanks for the help!
Welcome to Meteor!
The solution is straight forward once you understand that:
Subscription is just a stream of your DB documents from server into your client's MiniMongoDB. So your 2 subscriptions (it is perfectly fine having several subs on the same Collection) just fill in your client's buyList local collection.
Use of Collections client side is generally independent from how you subscribe the data. So you should simply use a similar selector and possibly options in your top3Trending and myTopViews helpers as you have done for your publication server side (not the same between the 2 helpers, obviously).
As a side note, you do not even need to fetch() the Collection cursor returned by find(), Blaze knows how to handle it directly.
I see a few problems with your code, first of all - your second subscription isn't going to work because your query is wrong:
Meteor.publish('myTopViews', function (){
var currentUser = this.userId;
return buyList.find(
{ ownerId:currentUser, newArrivalsExpiryDate : {'$lte': new Date()}},
{skip: 0, limit: 3}
);
});
You had ownerId: currentUser wrapped in curly braces, it is fixed above.
The way publications/subscriptions work is, if you have two publications sending different data, the template doesn't 'know' the data is coming from two different subscriptions. It will just have access to all of the data being sent by all subscriptions.
For that reason, your two helpers top3trending and myTopViews are returning exactly the same thing. You can delete one of them!
You should move your subscriptions out of the router and in to the Template itself. Here's an article that will help you with that!
There is a package percolate:find-from-publication that permits to filter the data from publications.
I am working on an edit form that has two paths. One is when the user clicks a "New" button, the other is when they click "Edit".
When they click "New", the code sets a form_id Session var to null and a client_id session variable to null, then does a Router.go('formEdit') to load the formEdit template/route.
In the formEdit.js, I do a reactive Template helper (I think that's what they are called, but anyway) like so:
Template.formEdit.form = function() {
var form;
if (Session.equals('form_id', null)) {
// Create empty form
form = {
title: null,
client_id: Session.get('client_id'),
header_fields: [],
form_fields: []
};
} else {
// Load form
form = Forms.findOne({_id: Session.get('form_id')});
}
return form;
}
Basically I check if the form_id was set or not, if so I load it from the Forms collection, if not I create a blank one. I thought this would be pretty simple, really.
The problem is that the created/found form object does not behave in a "reactive" way. If I add header_fields or form_fields the subsequent template code never updates. Both are in a {{#each}} like so:
<template name="formEdit">
...
{{#each header_fields}}
{{> headerFieldOutput}}
{{/each}}
...
{{#each form_fields}}
{{> formFieldOutput}}
{{/each}}
</template>
How do I make it such that I can push header_fields and form_fields onto the form and have the underlying template reactively update the {{#each}}'s?
I think you're going about it a little differently than what the reactive programming methodology in Meteor is expecting.
You're putting the 'display' logic in your template helper, rather than using the template scaffolding itself to do it.
So, declare a very simple template helper, something like this:
Template.formEdit.form = function () {
return forms.findOne(Session.get("form_id"));
};
And then, in your template scaffolding have something like this:
{{#if form}}
{{#with form}}
{{#each header_fields}}
etc...
{{/with}}
{{#else}}
[[insert your blank form scaffolding in here]]...
{{/if}}
Then, as you set your Session form_id variable, you can set it to null to invoke the {{#else}} portion.
There are more details than this (logic in the form submit click handler to identify if you are performing an update or an insert, for example) but hopefully you get the gist of it from this.
You should try to gain a better understanding about how cursors and reactive computations work, as it will help you better understand how to best use the reactive methodology. A good starting place is the parties example (watch the video and walk through the code manually). It's similar to what you're doing, and shows a good way of building your templates for when you don't have a 'selected' object.
Hope this helps!
Ok so I'm working with meteor! Woo hoo I love it so far, but I've actually run into an architecture problem (or maybe its super simple and i just dont know it yet).
I have a list of names that belong to a user. And a delete button that is aligned next to the name
name - x
name - x
name - x
and I want a functionality to click the 'x', and then proceed to clearing the name from the database using the meteor event handler. I'm finding trouble thinking about how I'm going to pass the name along with the click to proceed to delete it from the database.
I can't use a unique id in the template to call a document.getElementById() (unless I came up with an integer system that followed the database.)
Does anyone have a good thought on this?
Here is a complete working example:
html
<body>
{{> userEdit}}
</body>
<template name="nameChoice">
<p>
<span>{{name}}</span>
x
</p>
</template>
<template name="userEdit">
{{#each nameChoices}}
{{> nameChoice name=this}}
{{/each}}
</template>
js
Users = new Meteor.Collection(null);
if (Meteor.isClient) {
Meteor.startup(function () {
Users.insert({nameChoices: ['foo', 'bar', 'baz']});
});
Template.userEdit.nameChoices = function () {
return Users.findOne() && Users.findOne().nameChoices;
};
Template.nameChoice.events({
'click .remove': function () {
_id = Users.findOne()._id;
Users.update(_id, {$pull: {'nameChoices': this.name}});
}
});
}
This actually does a bunch of stuff you wouldn't do in a real application (defined a client-only Users collection, assumes there is only one user, etc). But the main takeaway is that you can use the data context in each nameChoice template to respond to the remove event. This approach can nearly always replace the need for coming up with your own artificial id system. Feel free to ask questions if any of this is unclear.
Meteor's reusable UI components is still far away on the roadmap. What's the best community approved way to create reusable components? A system based on Session seems so global.
Let's say I want to create 2 different chat channels on 1 page at the time. What do I do?
Presuming you're doing chat through collections...
I would make it such that a certain value is added to the chat JSON going into the MongoDB. Say for instance, user test sends a message hello world in chat box 1. The JSON I'd send would look something like
{name: 'test', message: 'hello world', num: 1}
Then, in my chat helper thing, wherever I'm displaying new chats, I'd use a get method like this
UI.registerHelper(getChat, function(n){return Messages.find({num: n});})
Which would be called in the HTML with
{{#each getChat 1}} or {{#each getChat 2}} or whatever, depending on how many chat boxes you have.
This would basically only return values that correspond to the specific chat box.
Good luck.
If you want to use something dynamic instead of fixed param value, you can try this
// In memory collection for demonstration purpose
Messages = new Meteor.Collection(null);
if (Messages.find().count() === 0) {
Messages.insert({name: 'test2', num: 1});
Messages.insert({name: 'test5', num: 2});
// [...] Init some test data
}
Template.test.helpers({
// you can also use a collection
chats: function() { return [{num:1}, {num:2}] },
});
// Took from Bob's example
UI.registerHelper('getChat', function(n){
return Messages.find({num: n});
});
And your template
<template name="test">
{{#each chats}}
{{! num refer to a property of 'this'}}
{{#each getChat num}}
{{name}} <BR />
{{/each}}
{{/each}}
</template>
You can read more about Custom Block Helpers in Blaze here
My template is getting rendered twice on first load. I notice this because in
Template.home.rendered = function() {
console.log('rendered'); // => this is printed out twice
console.log(Data.find({}).count()); // => this is initially 0, then 1 the second time
}
Furthermore, on the first load, no Data is available. Yet on the second load, the Data is there.
Does anyone know what this problem might be, and why the data only appears the second time?
You need to find a way to render the template when your data is available.
Using this template structure, the if block content, which happens to be the template displaying your data, will be rendered only when the myDataIsReady helper returns true. (thus triggering the rendered callback only once, with data immediately available).
<template name="displayData">
<p>This is my data : {{this}}</p>
</template>
<template name="home">
{{#if myDataIsReady}}
{{#each data}}
{{> displayData}}
{{/each}}
{{/if}}
</template>
You have to define a subscription handle (an object returned by Meteor.subscribe) in order to use it's reactive ready method : we'll reference it in the myDataIsReady helper to track data availability, and the helper will automatically rerun when the state of ready changes.
Meteor.startup(function(){
// this subscription should return your data subset
myDataHandle=Meteor.subscribe("myData");
});
Template.home.myDataIsReady=function(){
return myDataHandle.ready();
}
Template.home.data=function(){
return Data.find({});
}
But that's quite annoying for such a simple task.
That's why I suggest using the Iron Router which makes things way simpler !
Add it to your project using "mrt add iron-router", then in a client/router.js and client/router.html, use this boilerplate code :
Router.configure({
loadingTemplate:"loading"
});
Router.map(function(){
this.route("home",{
path:"/",
// we indicate which subscription has to be marked ready in order to load the template
waitOn:function(){
return Meteor.subscribe("myData");
}
// the result of this function will become our target template data context
data:function(){
return Data.find({});
}
});
});
<template name="home">
{{#each this}}
{{> displayData}}
{{/each}}
</template>
<template name="loading">
<p>Data isn't ready yet...</p>
</template>
As you can see, the Iron Router allows us to specify simply what we painfully achieved manually in the first code example (waiting on a particular subscription to render a template), and of course we get free routing, loading mechanisme, layout management, etc...
Search the web for a complete iron-router tutorial (my code is untested, but I hope it is ok and should get you started), it's so awesome that it's gonna be merged to Meteor ultimately.
I had a body.html in /client and a appBody.html in /client/templates, with, in iron router:
Router.configure({
layoutTemplate: 'appBody',
});
Both body templates were rendered (and happened to be the same). Obviously, the body.html in /client needed to be removed.