Meteor.renderList alway end up in [elseFunc] - meteor

I'm new to Meteor.
Trying to render items from collection but Meteor.renderList(observable, docFunc, [elseFunc]) alway go to elseFunc.
this.ComponentViewOrdersFlow = Backbone.View.extend({
template: null,
initialize: function() {
var frag;
Template.ordersFlow.events = {
"click a": function(e) {
return App.router.aReplace(e);
}
};
this.template = Meteor.render(function() {
return Template.ordersFlow();
});
console.log(Colors);
frag = Meteor.renderList(
Colors.find(),
function(color) {
console.log(color);
},
function() {
console.log('else consdition');
}
);
},
render: function() {
this.$el.html(this.template);
return this;
}
});
Initially I thought that Collection is empty, but console.log(Colors) shows that there are items in collection. Moreover if I use Meteor.render(... -> Template.colors({colors: Colors.find()}) ) it renders template end show Collection items there.
Meteor version 0.6.6.3 (Windows 7, 64bit)
Mongo - connected to MongoLab
Thank you for any help.
Jev.

Can't really explain this well in the comments, so here is a very, very simple example of using the Meteor template engine. This is a 100% functional app, showcasing basic reactivity. Note that I never call render() or renderList() anywhere.
All this app does is show a button, that when clicked, adds a number to a list. The number is reactively added to the list, even though I never do anything to make that reactivity explicit. Meteor's templates are automatically reactive! Try it out - this is all of the code.
numbers.html:
<body>
{{> numberList}}
</body>
<template name="numberList">
<ul>
{{#each numbers}}
<li>{{number}}</li>
{{/each}}
</ul>
<button>Click Me</button>
</template>
numbers.js:
var Numbers = new Meteor.Collection("numbers");
if (Meteor.isClient) {
Template.numberList.numbers = function() {
return Numbers.find();
};
var idx = 0;
Template.numberList.events({
"click button": function() {
Numbers.insert({
number: idx
});
idx++;
}
});
}

Related

Meteor publications/subscriptions not working as expected

I have two publications.
The first pub implements a search. This search in particular.
/* publications.js */
Meteor.publish('patients.appointments.search', function (search) {
check(search, Match.OneOf(String, null, undefined));
var query = {},
projection = {
limit: 10,
sort: { 'profile.surname': 1 } };
if (search) {
var regex = new RegExp( search, 'i' );
query = {
$or: [
{'profile.first_name': regex},
{'profile.middle_name': regex},
{'profile.surname': regex}
]
};
projection.limit = 20;
}
return Patients.find(query, projection);
});
The second one basically returns some fields
/* publications.js */
Meteor.publish('patients.appointments', function () {
return Patients.find({}, {fields: {'profile.first_name': 1,
'profile.middle_name': 1,
'profile.surname': 1});
});
I've subscribed to each publication like so:
/* appointments.js */
Template.appointmentNewPatientSearch.onCreated(function () {
var template = Template.instance();
template.searchQuery = new ReactiveVar();
template.searching = new ReactiveVar(false);
template.autorun(function () {
template.subscribe('patients.appointments.search', template.searchQuery.get(), function () {
setTimeout(function () {
template.searching.set(false);
}, 300);
});
});
});
Template.appointmentNewPatientName.onCreated(function () {
this.subscribe('patients.appointments');
});
So here's my problem: When I use the second subscription (to appointments.patients), the first one doesn't work. When I comment the second subscription, the first one works again. I'm not sure what I'm doing wrong here.
The issue here is you have two sets of publications for the same Collection. So when you refer to the collection in client there is now way to specify which one of the publication it has to refer too.
What you can do is, publish all data collectively i.e. all fields you are going to need and then use code on client to perform queries on them.
Alternatively, the better approach will be to have two templates. A descriptive code:
<template name="template1">
//Code here
{{> template2}} //include template 2 here
</template>
<template name="template2">
//Code for template 2
</template>
Now, subscribe for one publication to template one and do the stuff there. Subscribe to second publication to template 2.
In the main template (template1) include template2 in it using the handlebar syntax {{> template2}}

How can I delay a reactive Meteor template from updating an HTML template variable, when a Meteor.call() updates the database instantly?

I'm developing a keno game. When the user presses the start button a Meteor.Call() executes everything for that card pick. Including updating the user balance. I have a setTimeout for the winning numbers, so that they display over a period of about 20 seconds. The problem is that when the call is made, the balance updates instantly, and then the numbers start displaying with the delay. I not familiar with how to solve this. I appreciate any help.
server-side:
Meteor.methods({
process: function(){
// generate numbers
// update user balance
}
});
client-side:
Template.keno.events({
'click #start' : function(){
Meteor.call('process',function(err,numbers){
//setTimeout on displaying numbers
// as setTimeout displays numbers, balance already updated. I need to delay
// the balance update, until all numbers are displayed.
// otherwise, the player see that they won before all numbers come out.
});
}
});
** Update **
The only help I need is to understand how to make a variable like {{balance}} unreactive, until I finish the setTimeout, and then have it update. Should I be using sessions? Should I not use a template variable and instead, insert the balance with jquery? It's just a simple solution, the difficulty is that I don't know what function / method I'm looking for that can help me turn off the reactivity for a set amount of time, and then update, after the Meteor.call() for then numbers finishes it's setTimeout.
If I understand your situation correctly, you need the template {{balance}} expression to be set at a time you decide vs. when the collection gets a result from the server. So you could use Session to set a value when you like. Below is an example:
<body>
{{> game}}
</body>
<template name="game">
<button id="process">Process</button>
<div>{{firstNumber}}</div>
<div>{{secondNumber}}</div>
<div>balance: {{balance}}</div>
</template>
if (Meteor.isClient) {
Template.game.events({
'click #process': function (e, tmpl) {
Meteor.call('process', function (err, result) {
Session.set('firstNumber', result[0]);
setTimeout(function () {
Session.set('secondNumber', result[1]);
Session.set('balance', result[0] + result[1]);
}, 2000);
});
}
});
Template.game.helpers({
firstNumber: function () { return Session.get('firstNumber'); },
secondNumber: function () { return Session.get('secondNumber'); },
balance: function () { return Session.get('balance'); }
});
}
if (Meteor.isServer) {
function randomNumber () {
return Math.floor(Math.random() * 100);
}
Meteor.methods({
process: function () {
return [randomNumber(), randomNumber()];
}
});
}
Try to wrap your Meteor.call() inside the setTimeout() itself, like:
Template.keno.events({
'click #start' : function(){
setTimeout(function(){
Meteor.call('process',function(){
//do something.
});
}, 20000);
}
});
Maybe the solution is to use reactivity on a duplicated collection :
You set your main collection on server side only.
You create another collection on the client side that will be a duplicate collection but used only for display
Then you pusblish the main collection to the client.
On the client side, you set all required observer on it that will replicate all modification on the duplicated collection. But this way you can manage animation or any other wished features. All actions on client side will do call on server-side but it won't affect immediatly the templates because the templates only use the duplicated collections.
I hope it will help you.
OK, so I threw this demo together in 5 mins, works though.
Here's the demo: http://keno-test.meteor.com/
Of course it needs LOT's more work, but the delayed thing works.
HTML:
<head>
<title>keno-test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<input id="callCardThing" type="button" value="Start card-thing" />
<h1>Here are the cards!</h1>
<ul>
{{#each cards}}
<li>{{value}}</li>
{{/each}}
</ul>
</template>
JS:
Cards = new Meteor.Collection('cards');
if (Meteor.isClient) {
Deps.autorun(function () {
Meteor.subscribe("cards");
});
Template.hello.events({
"click #callCardThing": function (event) {
Meteor.call("doCardThingOnServer");
}
});
Template.hello.helpers({
cards: function () {
return Cards.find({});
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
Meteor.publish("cards", function () {
return Cards.find({});
});
});
Meteor.methods({
doCardThingOnServer: function () {
// I remove all the cards every time just for the demo…
Cards.remove({});
var numberOfcards = 10;
var counter = Meteor.setInterval(function () {
Cards.insert({value: 'whatever! no: '+numberOfcards });
numberOfcards--;
if (numberOfcards < 1) Meteor.clearInterval(counter);
}, 1500);
}
});
}
Ok, so how about conditional {{balance}} rendering?
var shouldRender = false;
setTimeout(function () {
shouldRender = true;
}, 2000);
Template.template_name.shouldRender = function () {
return shouldRender;
}
{{#if shouldRender}}
{{>balance}}
{{/if}}
Have a look at the Atmosphere animation package : https://atmosphere.meteor.com/package/animation!
I've just done this package to explore one way of doing animation on database reactivity.
You have to register a cursor and the template to animate. There is a project that will show you how to do that.
I hope it will help you.

Meteor - How to insert dynamically generated content into a collection?

I'm new to Meteor and barely understand any of it but let's say I have a collection called mycollection, declared way up top so it's available in both the client and server section:
mycollection = new Meteor.Collection('MyDumbCollection');
And then I have something like this:
if (Meteor.isClient) {
Deps.autorun(function() {
Meteor.subscribe('mycollectionSubscription');
});
Template.myshittytemplate.rendered = function() {
$("#heynow").prepend(shitfuck).dosomething();
godammit = thisfuckingthing;
//paraphrasing
mycollection.insert({thing1: thisfuckingthing});
};
}
if (Meteor.isServer) {
Meteor.publish('mycollectionSubscription', function () {
return mycollection.find();
});
};
And then in my template:
<template name="myshittytemplate">
<div id ="heynow">
{{#each mycollection}}
{{{thing1}}}
{{/each}}
</div>
</template>
What I'm trying to do is have the 'thisfuckingthing' html that's created in the #heynow div saved to the collection and published to everybody. If there's a way to make Meteor simply observe changes to the dom and save them, that's even better.
I do have autopublish uninstalled, if that makes a difference. Halp.
In client Template
Template.myshittytemplate.mycollection = function() {
return mycollection.find({}).fetch();
};
Template.myshittytemplate.rendered = function() {
$(function(){
$("#heynow").prepend(shitfuck).dosomething();
godammit = thisfuckingthing;
//paraphrasing
mycollection.insert({thing1: thisfuckingthing},function(err,_id){
if(err){
console.log(err);
return;
});
console.log(_id);
});
};
}
I needed this in the Client part:
Template.myshittytemplate.mycollection = function() {
return mycollection.find();
};
Hopes this helps somebody!

Possible bug when observing a cursor, when deleting from collection

I've run into an interesting possible bug, but in this case it may be caused by the lack of a function for me to use to delete a document when observing a collection. Or I am misusing observe... which could very well be the case!
Here is sample code that will reproduce the issue I'm having.
I'm using the devel branch as of this writing, so I'm not sure if this works in 0.3.5
observebug.html
<head>
<title>observebug</title>
</head>
<body>
{{> main}}
</body>
<template name="main">
<h1>Example showing possible bug in Meteor wrt observe</h1>
<div>
<p>Try to delete a note. You will notice that it doesn't appear to get deleted. However, on the server, the delete did occur. Refresh the page to see that the delete did in fact occur.</p>
<h2>Notes:</h2>
<ul>
{{#each notes}}
{{> note_row}}
{{/each}}
</ul>
</div>
</template>
<template name="note_row">
<li>{{title}} <button name="delete">delete</button></li>
</template>
observebug.js
// CLIENT
if (Meteor.is_client) {
Notes = new Meteor.Collection("notes_collection");
Meteor.autosubscribe(function () {
Meteor.subscribe("notes_subscription");
});
Template.main.notes = function () {
return Notes.find();
};
Template.note_row.events = {
"click button[name='delete']": function (evt) {
Meteor.call("deleteNote", this._id, function (error, result) {
if (!error) {
console.log("Note deletion successful.");
} else {
console.log("Error when deleting note.");
}
});
}
};
}
// SERVER
if (Meteor.is_server) {
Notes = new Meteor.Collection("notes_collection");
Meteor.methods({
"deleteNote": function (note_id) {
try {
Notes.remove(note_id);
return true;
} catch (e) {
return false;
}
}
});
Meteor.publish("notes_subscription", function () {
var notes = Notes.find({}, {sort: {title: 1}});
var self = this;
// What we're doing here is basically making an exact duplicate
// of the notes collection. A possible use-case for this would be
// to actually make changes to the copied collection on the fly,
// such as adding new fields without affecting the original
// collection.
var upsertHandler = function (note, idx) {
note.some_new_field = 100;
self.set("notes_collection", note._id, note);
self.flush();
};
var handle = notes.observe({
added: upsertHandler,
changed: upsertHandler,
removed: function (note, idx) {
// As far as I can tell, unset won't remove the document,
// only attributes of the document. I don't think there's
// a method to handle actually removing a whole document?
self.unset("notes_collection", note._id);
self.flush();
}
});
self.onStop(function () {
handle.stop();
self.flush();
});
});
// Add example notes
Meteor.startup(function () {
if (Notes.find().count() === 0) {
Notes.insert({title: "Note #1"});
Notes.insert({title: "Note #2"});
Notes.insert({title: "Note #3"});
Notes.insert({title: "Note #4"});
Notes.insert({title: "Note #5"});
Notes.insert({title: "Note #6"});
}
});
}
What you'll be seeing
When you start this application up, you'll see 6 example "notes", each with a delete button. I suggest having your console open so you can see the console.logs. Click the delete button on any note. The note does get deleted, however this doesn't get reflected back up to the client.
I suspect the issue lies in how I'm leveraging observe to create a copy of the collection (which I can then manipulate without affecting the original collection). I feel like I need a function that deletes a whole document, not just some attributes (unset).
EDIT: See the example in action over at http://observebug.meteor.com/
So I've got it figured out. I did indeed need to use observe, and there is no Meteor bug on that end. Just a lack of understanding of what was involved in what I was trying to accomplish. Luckily I found a great starting point within the Meteor code itself and wrote an adjusted version of the _publishCursor function, which I've called publishModifiedCursor.
Here are the adjusted project templates and code:
observe.html
<head>
<title>observe</title>
</head>
<body>
{{> main}}
</body>
<template name="main">
<h1>Example in trying to publish a modified copy of a collection</h1>
<div>
<h2>Notes:</h2>
<ul>
{{#each notes}}
{{> note_row}}
{{/each}}
</ul>
<p><button name="add">add note</button></p>
</div>
</template>
<template name="note_row">
<li>
<strong>Original title:</strong> <em>{{title}}</em><br />
<strong>Modified title:</strong> <em>{{__modified_title}}</em><br />
<button name="delete">delete</button>
</li>
</template>
observe.js
// CLIENT
if (Meteor.is_client) {
ModifiedNotes = new Meteor.Collection("modified_notes_collection");
Meteor.autosubscribe(function () {
Meteor.subscribe("modified_notes_subscription");
});
Template.main.notes = function () {
return ModifiedNotes.find();
};
Template.main.events = {
"click button[name='add']": function (evt) {
Meteor.call("addNote", function (error, result) {
if (!error) {
console.log("Note addition successful.");
} else {
console.log("Error when adding note.");
}
});
}
};
Template.note_row.events = {
"click button[name='delete']": function (evt) {
Meteor.call("deleteNote", this._id, function (error, result) {
if (!error) {
console.log("Note deletion successful.");
} else {
console.log("Error when deleting note.");
}
});
}
};
}
// SERVER
if (Meteor.is_server) {
Notes = new Meteor.Collection("notes_collection");
Meteor.methods({
"addNote": function () {
try {
Notes.insert({title: "Note #" + (Notes.find().count() + 1)});
return true;
} catch (e) {
return false;
}
},
"deleteNote": function (note_id) {
try {
Notes.remove(note_id);
return true;
} catch (e) {
return false;
}
}
});
Meteor.publish("modified_notes_subscription", function () {
// Pull up the original notes_collection
var notes = Notes.find({}, {sort: {title: 1}});
// Publish a near identical collection, with modifications
this.publishModifiedCursor(notes, "modified_notes_collection", function (note) {
note.__modified_title = getTitleModifiedByServer(note.title);
return note;
});
});
var getTitleModifiedByServer = function (title) {
return title + "!!!";
};
// Add example notes
Meteor.startup(function () {
if (Notes.find().count() === 0) {
Notes.insert({title: "Note #1"});
Notes.insert({title: "Note #2"});
Notes.insert({title: "Note #3"});
Notes.insert({title: "Note #4"});
Notes.insert({title: "Note #5"});
Notes.insert({title: "Note #6"});
}
});
_.extend(Meteor._LivedataSubscription.prototype, {
publishModifiedCursor: function (cursor, name, map_callback) {
var self = this;
var collection = name || cursor.collection_name;
var observe_handle = cursor.observe({
added: function (obj) {
obj = map_callback.call(self, obj);
self.set(collection, obj._id, obj);
self.flush();
},
changed: function (obj, old_idx, old_obj) {
var set = {};
obj = map_callback.call(self, obj);
_.each(obj, function (v, k) {
if (!_.isEqual(v, old_obj[k])) {
set[k] = v;
}
});
self.set(collection, obj._id, set);
var dead_keys = _.difference(_.keys(old_obj), _.keys(obj));
self.unset(collection, obj._id, dead_keys);
self.flush();
},
removed: function (old_obj, old_idx) {
old_obj = map_callback.call(self, old_obj);
self.unset(collection, old_obj._id, _.keys(old_obj));
self.flush();
}
});
self.complete();
self.flush();
self.onStop(_.bind(observe_handle.stop, observe_handle));
}
});
}
See it live over at http://observebug.meteor.com/. The final effect is underwhelming, since it's just a test involving adding/removing notes... but hey now it works!
Hope this helps someone else out in the future.
this.unset requires three parameters, the first and second one are correct; it however requires a third parameter to tell which attributes to unset. This is a bug in your code and not in Meteor.
However, note that when your callback is called tha the document is already deleted from the collection and that you are just working on a copy of the object. Exactly, Notes.find({}).count() reveals the remaining count at any point during your callback.
// I don't think there's
// a method to handle actually removing a whole document?
If you want two collections, create two collections. By only keeping one and trying to do all sorts of magic, you are calling functions that are not even meant to be doing what you want them to do. If you just create
Notes_copy = new Meteor.Collection("notes_collection_copy");
and use that to keep track of temporary notes, you won't run into any of the trouble you had now.

how to trigger JQuery .draggable() on elements created by templates?

I have a standard template in an Html file like:
<template name="cards">
{{#each all_cards}}
{{> card_item}}
{{/each}}
</template>
<template name="card_item">
<div class="card" style="left:{{position.x}}px; top:{{position.y}}px">
{{title}}
</div>
</template>
I want to have the cards (css selector .card) become draggable with JQuery.
Now since Meteor automagically updates the DOM using the template, when and how do I know where to call .draggable() on what??
EDIT: This is so far my solution which makes pending movements on other client visible with a wobble animation (using CSS3):
Template.card_item.events = {
'mouseover .card': function (e) {
var $target = $(e.target);
var $cardContainer = $target.hasClass('card') ? $target : $target.parents('.card');
$cardContainer.draggable({containment: "parent", distance: 3});
},
'dragstart .card': function (e) {
Session.set("dragging_id", e.target.id);
$(e.target).addClass("drag");
pos = $(e.target).position();
Events.insert({type: "dragstart", id:e.target.id, left: pos.left, top: pos.top});
},
'dragstop .card': function (e) {
pos = $(e.target).position();
Events.insert({type: "dragstop", id:e.target.id, left: pos.left, top: pos.top});
Cards.update(e.target.id, {$set: {left:pos.left, top:pos.top}});
Session.set("dragging_id", null);
}
}
Events.find().observe({
added: function(event) {
if (event.type == "dragstart" && !Session.equals("dragging_id", event.id)) {
$("#"+event.id).draggable({disabled: true});
$("#"+event.id).addClass("wobble");
}
if (event.type == "dragstop" && !Session.equals("dragging_id", event.id)) {
$("#"+event.id).animate({left: event.left, top: event.top}, 250);
Events.remove({id:this.id});
$("#"+event.id).draggable({disabled: false});
}
}
});
EDIT: This approach doesn't seem to work in the latest versions of Meteor, e.g. v0.5.0. See my comment below.
Looks like we're working on similar things! I've got a working proof of concept for a simple Magic: The Gathering app. Here's how I have dragging implemented at the moment:
In a <head> section in one of your html files, include the jQuery UI script:
<script src="jquery-ui-1.8.20.custom.min.js"></script>
Then, in a js file, make sure elements become draggable on mouseover (note: this is sub-optimal on touchscreens since it requires two touches to drag... I'm looking for a better touchscreen solution):
Template.card_item.events['mouseover .card, touchstart .card'] = function (e) {
var $target = $(e.target);
if (!$target.data('isDraggable')) {
$target.data('isDraggable', true).draggable();
}
};
And finally, handle the drag and dragstop events:
var prevDraggedTime = 0
Template.card_item.events['drag .card'] = function (e) {
// get the cardId from e
var now = new Date().getTime();
var position;
if (now - prevDraggedTime > 250) {
position = $(e.target).position();
Cards.update(cardId, {$set: {x: position.top, y: position.left}});
prevDraggedTime = now;
}
}
Template.card_item.events['dragstop .card'] = function (e) {
// get the cardId from e
var position = $(e.target).position();
Cards.update(cardId, {$set: {x: position.top, y: position.left}});
}

Resources