I have an app that subscribes to 4 collections (the collections are very small 1 to 20 records each). But the amount of time it takes to load these collections is huge.
One of them is just 13 records, and it takes several seconds to load its template. Is it normal? (I'm still testing on meteor servers)
this is a sample of the code :
Meteor.subscribe('trackedUser', function() {
console.log('finished fetching trackedUser');
Template.users.rendered = function() {
/*handlign of the template*/
console.log('users template rendered');
}
});
/*observe geolocation after it is all fetched*/
Meteor.subscribe('geolocation', function() {
console.log('finished fetching location');
/* Obseve the Collection geolocation and trigger event when item inserted or removed */
query = Geolocation.find({});
query.observeChanges({
added: function(id) {
addMarkerToMap(id);
window.map.fitBounds(group.getBounds());
return;
}
});
});
});
And this is my template
<template name="users">
<ul id="item-list">
{{#each trackedUser}}
<li id="{{_id}}">
<input type="checkbox" checked />
<span><select name="colorpicker">
{{#each color}}
<option value="{{mColorCode}}" {{selected ../mColor mColorCode}}>{{mColorName}}</option>
{{/each}}
</select>
</span>
<img width="40" src="data:image/png;base64,{{mImage}}" />
<span class="name">{{mUsername}}</span>
<p><span class="description">{{mDescription}}</span></p>
</li>
{{/each}}
</ul>
</template>
Thanks
I was able to solve this issue by adding a condition to the definition of the template content that is false while loading the page, i.e., doing the initial sync, and only activating that content when it was loaded.
Before (10s page load for 300 records being published by the server):
Template.itemlist.items = function () {
return Item.find({type: 'car'},
{sort: {start: -1},
limit: 30});
};
To (2s page load for 3000 records published by the server):
Template.itemlist.items = function () {
if (Session.get("active")) {
return Item.find({type: 'car'},
{sort: {start: -1},
limit: 30});
} else {
return [];
}
};
To "activate" the session only once the data was loaded, I added:
Deps.autorun(function () {
Meteor.subscribe("Item",
{
onReady: function() {
Session.set("active", true);
}
});
});
I recently had to diagnose a performance issue with a meteor app and found that it wasn't the rendering of the template that was slow, but rather the transferring of data between the server and the browser.
You can use the Chrome developer tools to diagnose the issue.
Open the tool and make sure it's profiling Network traffic
Load your site
Examine the network traffic. Did it take a long time for the data to finish coming to your browser?
If the data is taking a long time to finish coming to your browser then you can dive deeper into the websocket traffic:
Filter for websocket traffic (bottom of the network tab)
Select the websocket request to view the frames
Examine the information in the frames. Is it a lot?
You may find (as I did) that you are transferring a lot of information to the browser that isn't needed to render the templates.
In that case, make sure you've disabled autopublish and then publish just the data you need by using the field specifier.
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 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.
I am trying to use meteor autosubscribe function on the client but sometimes it works and sometimes it doesn't. So here is the case:
Working version: I have dropdown which is populated with channels. When user clicks on the channel I set session variable and start loading threads:
Template.channelDropdown.events({
"click #channelLink": function() {
Session.set("currentChannel", this);
}
});
html
<ul class="dropdown-menu">
{{#each channels}}
<li>
<a id="channelLink" href="#">{{name}}</a>
</li>
{{/each}}
</ul>
and
Tracker.autorun(function() {
Meteor.subscribe("threadsByChannel", Session.get("currentChannel"));
});
Meteor.publish("threadsByChannel", function (channel) {
return threads.find({channel: channel});
});
and loading threads:
"channelThreads": function() {
return threads.find({channel: Session.get("currentChannel")}).fetch();
},
Now this works. However I have other method to open channel which doesn't work. It is possible to enter channel name and if it doesn't exist it is created, otherwise existing one is returned.
Template.channelSearchBar.events({
"submit #joinChannelForm": function() {
event.preventDefault();
var channelName = $("#channelNameField").val();
Meteor.call("getChannelByName", channelName, function(error, result) {
if (error) {
// TODO error handling
} else {
Session.set("currentChannel", result);
}
});
$("#channelNameField").val("");
}
});
server:
'getChannelByName': function (channelName) {
var channel = channels.findOne({name: channelName});
if (channel) {
return channel;
} else {
var newChannel = {
name: channelName
}
return channels.insert(newChannel);
}
}
html
<template name="channelSearchBar">
<form id="joinChannelForm" class="navbar-form navbar-left" role="search">
<div class="form-group">
<input id="channelNameField" type="text" class="form-control" placeholder="Enter channel name">
</div>
<button type="submit" class="btn btn-default">Join</button>
</form>
</template>
Now the only difference is that session variable is set in callback. I'm pretty sure this is the problem as it is asynchronious call to the server and somehow threads are not populated in client when requested. When I set breakpoint in loading threads function (threads.find() on client), I see that session variable is correctly set, but it just does not return anything. Also sometimes it is called two times (for example in working first case first call returns nothing and then second call returns real results for some reason. Is this is how it suppose to work?). I am just beginning to learn meteor and trying to understand how it all works. Would be glad if someone could explain or direct me to the right way.
EDIT: Its very strange. I have put breakpoint in publish function and it seems it works fine - exactly like it should. However on the not working case it simply returns nothing right from the server side even though both working and not working situations provides (seemingly) exactly the same channel object. It seems that the problem is related with mongodb query.
Why don't remove the Meteor.call, and do everything on the client side?, the subscription on the Autorun seems to be fine, lets try with this code, just make sure you have the allow/deny permissions in order.
Template.channelSearchBar.events({
"submit #joinChannelForm": function() {
event.preventDefault();
var channel = channels.findOne({name: channelName}),
channelName = $("#channelNameField").val();
if (channel) {
return channel;
} else {
var newChannel = {
name: channelName
}
var chanelCreated = channels.insert(newChannel);
Session.set("currentChannel", chanelCreated);
$("#channelNameField").val("");
}
}
});
OK it seems the real problem was not that of meteor publish/subscribe mistake but because of mongodb query which was not recognizing channel object. Problem was solved by changing this:
threads.find({channel: channel})
to this:
threads.find({"channel.name": channel.name})
I have found that mongo queries cares about order of object parameters, but channel had only one parameter (name) at the moment, so I'm still not sure why they were not considered equal. One channel was returned from findOne query and another from find. One from find was recognized.
I have a collection published on the server and auto-subscribed on the client. I'd like to set the 'selected' item on the session and have the template update to display only the selected item, but it seems this can only be done with a roundtrip to the server (which is totally unnecessary).
Common:
var Missions = new Meteor.Collection('missions');
Client:
Template.missionList.missions = function() {
var currMission = Session.get('selectedMission');
var searchMission = {};
if(currMission)
{
searchMission['_id'] = currMission;
}
return Missions.find(searchMission);
};
Template.missionList.events({
'click div.mission': function (e, t) {
Session.set('selectedMission',
this._id == Session.get('selectedMission') ? null : this._id
);
}
});
Template.mission.isSelected = function() {
return this._id == Session.get('selectedMission');
};
Meteor.autosubscribe(function () {
Meteor.subscribe("missions");
});
Server:
Meteor.publish('missions', function() {
// there are really some filters here, but removed for simplicity
return Missions.find();
});
Template:
<template name="missionList">
<div class="missionList">
{{#each missions}}
{{> mission}}
{{/each}}
</div>
</template>
<template name="mission">
<div class="mission{{#if isSelected}} selected{{/if}}">details</div>
</template>
My requirement is for the Missions.find() in Template.missionList.missions to filter the client-side cached results, rather than to re-request from the server, but I can't seem to find a flag or settings to allow me to tell minimongo to only use the currently available data.
I'm also not entirely sure if this is what I should be doing, I started out just using jQuery to hide the non-selected missions but getting my head round Meteor and it seems a natural fit to use the data and reactivity to drive selection/local-filtering.
Is there any way the roundtrip can be avoided or am I just using it wrong?
By setting up a publish / subscribe relationship, you are creating a simplified form of database replication. Minimongo will have a copy of the data locally and execute the find() locally without a server roundtrip. If you are seeing network activity or calls to the server code, it is because meteor is regularly working behind the scenes to keep the subscription in sync with the server, not for your specific find.
This also means you have to wary of sending too much data to the client, so your server side publish function may want to filter by the specific fields needed by client, in addition to existing your selection criteria.
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())