The most concise and elegant way to break the reactivity of helpers in meteor - collections

I want to use my collection players in an helper... for several reasons I would like this collection will be not reactive. I would like just a first call to the database to display the collection. I tried to use reactivates:false option but in this case the collection remains empty after loading and nothing display.
Template.myGame.helpers({
players: function () {
return Players.find({}, {reactive: false});
}
})
<ul>
{{#each players}}
{{> player}}
{{/each}}
</ul>

You're effectively looking for a way to return data non-reactively, but only once it's ready. This can be achieved with a subscription handle (assuming you've removed "autopublish").
playersSub = Meteor.subscribe('players', ...);
Template.myGame.helpers({
players: function() {
if (playersSub.ready()) {
return Players.find({}, {reactive: false});
} else {
return [];
}
}
});
NB - it is actually possible to get the subscription readiness (non-reactively) without having a handle, by using Players._connection._subscriptions.[SUB_ID].ready, but I wouldn't recommend this as it's not part of the public API.

Related

Nested elements not available without autopublish in Meteor

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.

I misunderstood Session what is a good alternative?

Currently I'm creating an app with meteor en learning it while building. I try to incorporate Sessions instead of writing everything to the database (what I was doing). In my understanding Session is a global object which stores key-value pairs and is reactive. Therefore I thought it would a great choice to use for template rendering specifics in my simple game. My goal is small game, and the different steps will be renderend in a template for each player based on certain action they made.
I rewrote my app and wanted to use Session in this way (simplified of course).
My template:
<template name="gameRoom">
<button id='click'>click</button>
{{#if lastAction}}
{{>waiting}}
{{/if}}
</template>
Template.gameRoom.events({
lastAction: function() {
return Session.get('lastAction') === Meteor.userId();
};
})
Template.gameRoom.helpers({
'click #click' : function() {
Session.set('lastAction', Meteor.userId());
};
})
However this doesn't work they way I thought it would work. It looks like that each Session is individual for each user (what makes sense of course considering it's (sort-of) a replacement of cookies).
So my question is:
Is there a good alternative for Sessions? Do i really need to have a
alternative or is using a database ok for this? What about a local database?
Your events and helpers functions are backwards, you're missing a couple curly braces, and your event key (the button's ID) is wrong. Try this:
Template.gameRoom.helpers({
lastAction: function() {
return Session.equals('lastAction', Meteor.userId());
}
});
Template.gameRoom.events({
'click #click': function() {
Session.set('lastAction', Meteor.userId());
}
});
Edit: from what you are trying to do, it might make sense to do something like this:
Actions = new Meteor.Collection('actions');
if (Meteor.isClient) {
Template.gameRoom.events({
'click #click': function() {
Actions.insert({userId: Meteor.userId()});
}
});
Template.gameRoom.helpers({
lastAction: function() {
var lastAction = Actions.findOne() || {};
return lastAction.userId === Meteor.userId();
}
});
}

Meteor - Helper comparing two different cursors

I'm using a template helper that returns a comparison between a specific cursor and every iteration of the documents from another cursor. The 'inside' value is stored inside the 'City' collection.
I know that storing a unique 'inside' value on each of the documents inside the 'Places' collection would solve this problem, but you only can be 'inside' one place in the each 'City', this would be a performance issue.
Helpers:
Template.listPlaces.helpers({
places: function () {
return Places.find({});
},
insidePlace: function () {
return City.findOne({_id: this._id}).inside === places._id;
}
]);
Template:
<ul>
{{#each places}}
{{#if insidePlace}}Active{{else}}Inactive{{/if}}
{{/each}}
</ul>
I know a solution would be to have a cursor observer running that would update a Session variable with the 'inside' value each time City.inside gets updated, but I would like to know if there is a better solution.
Have you considered using a transform?
Template.listPlaces.helpers({
places: function () {
var transform = function(doc) {
var city = Cities.findOne({_id: doc._id});
doc.insidePlace = (city.inside == doc._id)
return doc;
}
return Places.find({}, {transform: transform});
},
]);

Meteor Group Collection by Field

I am trying to return a collection (Postings) grouped by a field (status). I am pretty new to mongo and meteor. The query below gives me the collections grouped by status with # of docs by that status... basically I want the same thing but have the actual documents in there.
Also, I would like to be able to publish/subscribe to this so that they reactivly update. I am creating an admin dashboard that groups all the Postings by current status.
A friend provided the following gist, but it is a bit over my head: https://gist.github.com/ryw/8827179
db.postings.group({ key: {status: 1}, initial: {sum:0}, reduce: function(doc, prev) { prev.sum += 1; } })
Thanks!
If you need all of the documents on the client, then I would just publish the whole collection and let the template code group them.
client
Tracker.autorun(function() {
if (Meteor.user()) {
Meteor.subscribe('allPostings');
}
});
Template.admin.helpers({
postings: function() {
if (Session.get('currentStatus')) {
return Postings.find({status: Session.get('currentStatus')});
}
},
statuses: function() {
return _.uniq(_.pluck(Postings.find().fetch(), 'status'));
}
});
Template.admin.events({
'click .status': function() {
Session.set('currentStatus', String(this));
}
});
<template name="admin">
<div class="left-panel">
<ul>
{{#each statuses}}
<li class="status">{{this}}</li>
{{/each}}
</ul>
</div>
<div class="right-panel">
<ul>
{{#each postings}}
<li>{{message}}</li>
{{/each}}
</ul>
</div>
</template>
server
Meteor.publish('allPostings', function() {
var user = Meteor.users.findOne(this.userId);
if (user.isAdmin) {
return Postings.find();
}
});
I'm assuming you have some way to identify admin users (here I used isAdmin). I am also assuming that a posting has a status and a message.
Instead of using aggregate functions or map reduce operations, you could denormalize your data and store a separate collection of the groups and their counts.
You can update your counts using observe functions as in the following example from the relevant section of meteor docs:
// Keep track of how many administrators are online.
var count = 0;
var query = Users.find({admin: true, onlineNow: true});
var handle = query.observeChanges({
added: function (id, user) {
count++;
console.log(user.name + " brings the total to " + count + " admins.");
},
removed: function () {
count--;
console.log("Lost one. We're now down to " + count + " admins.");
}
});
// After five seconds, stop keeping the count.
setTimeout(function () {handle.stop();}, 5000);
This way, you can present the groups and their counts on a template and it would be reactive.

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