Reactive updates not received in other browsertabs - meteor

Meteor promises reactive updates, so that views are auto-updated when data changes. The included leaderboard example demonstrates this. It runs fine when I test it: data is updated across several browsertabs in different browsers, as expected.
All set and go, I started coding with meteor and progress was being made, but when I tested for reactive updates across browertabs, I noticed that only after a short while the updates across tabs stopped.
I boiled down the problem to the following code, based on a new empty meteor project:
updatebug.html
<head>
<title>updatebug</title>
</head>
<body>
{{> form}}
</body>
<template name="form">
<form onsubmit="return false;">
{{#each items}}
{{> form_item }}
{{/each}}
</form>
</template>
<template name="form_item">
<div>
<label>{{name}}
<input type="text" name="{{name}}" value="{{value}}">
</label>
</div>
</template>
updatebug.js:
Items = new Meteor.Collection("items");
if (Meteor.is_client) {
Template.form.items = function () {
return Items.find();
};
Template.form_item.events = {
'blur input': function(e) {
var newValue = $(e.target).val();
console.log('update', this.name, this.value, newValue);
Items.update({_id: this._id}, {$set: {value: newValue}});
},
};
}
if (Meteor.is_server) {
Meteor.startup(function () {
if (Items.find().count() === 0) {
Items.insert({name: 'item1', value: 'something'});
}
});
}
Run in multiple browsertabs, start changing the value of the input in one tab. The other tabs will reflect the change. Goto the next tab and change the value. Repeat a couple of times.
After a while, no more updates are received by any other tabs. It seems that once a tab has changed the value, it does not receive/show any more updates.
Differences compared to the leaderboard example (since it's very similar):
The leaderboard uses no form controls
The leaderboard example does an increment operation on update, not a set
I am about to file a bug report, but want to be sure I am not doing anything stupid here, or missing an essential part of the Meteor Collection mechanics (yes, autopublish package is installed).

The issue here is input element preservation. Meteor will preserve the input state of any form field with an id or name attribute across a template redraw. The redraw is preserving the old text in your form element, because you wouldn't want to interrupt another user typing in the same field. If you remove the name attribute from the text box, each tab will update on blur.
In fact, I'm not sure why the first update works in your example. That may actually be the bug!
You can see it's not a data problem by opening the console in each browser. On each blur event you will get an updated document in every open tab. (Type Items.find().fetch())

Related

Meteor Iron:Router Template not Rendering

I have a main page which lists a few text items ("Ideas"), which are clickable links. Clicking on them should take you to a page where you can edit them. Here's my html:
<head>
<title>Ideas</title>
</head>
<body>
</body>
<template name="Ideas">
<ul>
{{#each ideas}}
{{> idea}}
{{/each}}
</ul>
</template>
<template name="idea">
<li>{{text}}</li>
</template>
<template name="ShowIdea">'
<div class="editable" contentEditable="true">{{text}}</div>
</template>
I've added Iron:Router to my project to allow for moving between the pages. Here's the javascript:
Ideas = new Mongo.Collection("ideas");
if (Meteor.isClient) {
Router.route('/', function() {
this.render('Ideas');
});
Router.route('/idea/:_id', function() {
var idea = Ideas.findOne({_id: this.params._id});
this.render('ShowIdea', {text: idea.text});
});
Template.Ideas.helpers({
ideas: function () {
return Ideas.find({});
}
});
}
I inserted a single idea to my Mongo DB using the Meteor Mongo command line tool. That single item shows up properly on my main page. Here's what the HTML looks like in my debugger for the main page:
<html>
<head>...</head>
<body>
<ul>
<li>
The first idea ever
</li>
</ul>
</body>
</html>
Clicking on that link takes me to a new page with an address of:
http://localhost:3000/idea/ObjectID(%22550b7da0a68cb03381840feb%22)
But nothing shows up on the page. In the debugger console I see this error message + stack trace, but it means nothing to me since it all seems to be pertaining to iron-router and meteor, not code which I actually wrote:
Exception in callback of async function: http://localhost:3000/Idea.js?2fd83048a1b04d74305beae2ff40f2ea7741d40d:10:44
boundNext#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:424:35
http://localhost:3000/packages/meteor.js?e53378596562e8922a6369c955bab1e047fa866b:978:27
onRerun#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:520:13
boundNext#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:424:35
http://localhost:3000/packages/meteor.js?e53378596562e8922a6369c955bab1e047fa866b:978:27
onRun#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:505:15
boundNext#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:424:35
http://localhost:3000/packages/meteor.js?e53378596562e8922a6369c955bab1e047fa866b:978:27
dispatch#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:448:7
_runRoute#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:543:17
dispatch#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:844:27
route#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:710:19
boundNext#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:424:35
http://localhost:3000/packages/meteor.js?e53378596562e8922a6369c955bab1e047fa866b:978:27
boundNext#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:371:18
http://localhost:3000/packages/meteor.js?e53378596562e8922a6369c955bab1e047fa866b:978:27
dispatch#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:448:7
http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:390:21
_compute#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:308:36
Computation#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:224:18
autorun#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:499:34
http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:388:17
nonreactive#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:525:13
dispatch#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:387:19
dispatch#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:1688:22
onLocationChange#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:1772:33
_compute#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:308:36
_recompute#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:322:22
flush#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:452:24
And then it ends with this warning message:
Route dispatch never rendered. Did you forget to call this.next() in an onBeforeAction?
I don't have an onBeforeAction (I'm not even sure what that is)... so I don't think that message pertains to me?
I just started using Meteor the other day and just added iron-router not 24 hours ago, so I'm a bit lost here. Any pointers on how I can debug and fix this would be great.
Two things need fixing:
When you insert documents from the shell they are assigned _id values which are mongo ObjectIDs, whereas meteor defaults to using strings. This explains the weird URL. To avoid this problem, it's generally best to initialize your data from the server. Here's an example:
if (Meteor.isServer) {
Meteor.startup(function() {
if (Ideas.find().count() === 0) {
Ideas.insert({text: 'feed the cat'});
}
});
}
Now after a $ meteor reset you will always start with one cat-related idea.
If you wish to pass a context to your template, you'll need to use the data attribute like so:
Router.route('/idea/:_id', function() {
this.render('ShowIdea', {
data: function () {return Ideas.findOne({_id: this.params._id})}
});
});
See this example from the docs. After making those changes, the code worked correctly for me.

sending information through events in meteor

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.

Re-rendering of a template doesn't allow me to permanently change an element's class?

I have a sortable list.
<template name="the_playlist">
{{#each main_list}}
<li id="{{index}}" class="list_element">
<div class="next_song">...</div>
<div class="destroy">...</div>
<div class="element_style">{{song_title}}</div>
</li>
{{/each}}
</template>
And this is the main_list that it prints from.
Template.the_playlist.main_list = function(){
//if ret is valid, it will have a songs member
var ret = Links.find().fetch()[0];
if (typeof ret == 'undefined'){
ret = []
}
else {
ret = Links.find().fetch()[0].songs;
}
return ret;
}
And I am using the sortable plugin and more importantly its update callback which updates everytime the user changes a position the list or an element is added to the list.
$(function() {
$( "#playlist" ).sortable({
update: function(){
Template.list.updateList(); //MODIFIES DB CONTENTS, AND MAIN_LIST's VALUES CHANGE
}});
$( "#playlist" ).disableSelection();
});
*The problem: * If a page already has list elements when it's loaded, for one time only, I would like to add a class that hides (.addClass("hide")) each of the next_song elements that are on the page at that time. This *will work only until main_list changes* by a call to Template.list.updateList above, after which automagically, the added class will disappear - most likely due to the re-rendering that is occuring since the main_list depends on the db changes.
The following in the JQuery snippet I use to try and accomplish this.
$("#playlist li .next_song").each(function(){
$(this).addClass("hide_song");
})
Here is a demo. Try plugging in the above JQUery code into the console. and then move the list elements around to see the problem.
Can you not just determine whether that will be the case in a helper function?
Template.the_playlist.helpers({
'list_elements_exist': function() {
return (!!$('#playlist li').length);
}
}
Then you can just insert the logic straight into the template:
<div class="next_song{{#if list_elements_exist}} hide_song{{/if}}">...</div>
To be honest, I'm not 100% sure that this will float with reactivity depending on the structure of your app. If it doesn't work properly, I'd introduce a new session boolean, list_elements, the value of which is returned by the helper function above. It should be fairly easy to update its value in event handlers or created callbacks to keep it tracking whether there are any items in the list or not, and this will guarantee the list renders as required regardless of other dependencies changing.

Meteor template gets rendered twice

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.

Template is re-rendered even though there is no data change

I'm struggling with an issue that I will explain giving a simple demo.
There's following very simple document in People Collection.
{
"_id" : "vxmLRndHgNocZouJg",
"fname" : "John" ,
"nicks" : [ "Johnny" , "Jo"]
}
Now let's consider following templates. Basically I display username and a list of nicknames with input field for adding more nicknames.
<head>
<title>test</title>
</head>
<body>
{{> name}}<br/>
{{> nicks}}
</body>
<template name="name">
<input type="text" value="{{fname}}"/>
</template>
<template name="nicks">
{{#each nicks}}
<div>{{this}}</div>
{{else}}
no nicks yet
{{/each}}
<input type="text" name="nicks"/>
<input type="submit"/>
</template>
My client javascript code is as follows:
Template.name.fname = function() {
return People.findOne({"fname" : "John"},{
transform : function(doc) {
return doc.fname;
}
});
}
Template.name.rendered = function() {
console.log('Template "name" rendered!');
}
Template.nicks.nicks = function() {
var john = People.findOne({"fname" : "John"});
if(john) return john.nicks;
}
Template.nicks.events({
'click input[type="submit"]' : function () {
var johnId = People.findOne({"fname" : "John"})._id; // demo code
People.update(johnId,{
$addToSet : {
nicks : $('input[name="nicks"]').val()
}
})
}
});
My problem is that after adding nickname (update of nicks field in a document) template name is re-rendered (I know because I console.log it). When I query People collection to provide data for name template I use transform option so changes in nicks field shouldn't have impact on name template.
Meteor docs supports this:
Cursors are a reactive data source. The first time you retrieve a cursor's documents with fetch, map, or forEach inside a reactive computation (eg, a template or autorun), Meteor will register a dependency on the underlying data. Any change to the collection that changes the documents in a cursor will trigger a recomputation.
Why template name is re-rendered then?
The template is re-rendered because you change the People collection.
When you alter the People collection, Meteor automatically assumes that everything that it provides data to needs to be recalculated. (Which your name template does via Template.name.fname.
Even though you transform the output of the cursor, the People collection has changed in some way. The query is done before the transform is used, in other words, its not the transform that is looked at but the whole collection.
Meteor thinks that perhaps your document with {'fname':'John'} may have some other field that might have changed and it needs to requery it to check (which the nicks field has been altered). The transform is then applied after the requery.
Your HTML might not actually change at this point, only if the cursor returns something different will the html be changed.
If it becomes an issue in any scenario (i.e forms being cleared or DOM being changed when it shouldn't be) you can use the {{#isolate}} {{/isolate}} blocks to ensure that only everything inside them is re-rendered and nothing outside.

Resources