Dear all,
Template.tmp_detail_campaign_code_batch.events({
'click .ancProdCodePagination': function (e) {
Meteor.subscribe('ItemPage', Number*10,10)
}
});
Its definition:
Meteor.publish('ItemPage', function(skipItem, takeItem){
return Item.find({},{
skip : skipItem,
limit : takeItem
}); }
When I click .ancProdCodePagination, the amount of subscribed Items keep increasing by 10. For pagination, I'd like to make the amount stay at 10, but with different Items for each click.
What should I do?
You just need to stop the previous subscription first, which will involve storing the handle that it returns somewhere:
var itemSub;
Template.tmp_detail_campaign_code_batch.events({
'click .ancProdCodePagination': function (e) {
if (itemSub)
itemSub.stop();
itemSub = Meteor.subscribe('ItemPage', Number*10,10);
}
});
From the docs, the stop method does this:
Cancel the subscription. This will typically result in the server directing the client to remove the subscription's data from the client's cache.
Related
I currently havea widget which grabs hundreds of documents from the DB via subscription and then keep listening for new documents, so it can update a stock chart.
There is a problem tough, which is every time the data is updated the chart is updated, which causes a redraw.
This is a problem cause it's calling redraw hundreds of time at the beginning even tough it just need to "fetch all data then draw and wait for updates", the updates will then happen not very often, so then it would be ok to redraw.
my current code:
Template.nwidget.onRendered(function() {
return this.autorun(function() {
var data;
data = {};
data = Data.find({
type: 'my_type'
});
data = data.fetch();
return update(data);
});
});
For doing some after data subscription you can do like this:
Meteor.subscribe( 'collection', {
onStop: function( error /* optional */ ) {
// when the sub terminates for any reason,
// with an error argument if an error triggered the stop
},
onReady: function() {
// when ready
}
});
If you want to render page after the data subcribe then you can add waitOn in your router.
There is one more way to check where subscription is ready or not. If subscription is not ready you can show something else like a loading screen.
var handle = Meteor.subscribe( 'collection');
Tracker.autorun(function() {
if (handle.ready())
//write whatever you want to do here.
});
For the auto update in your view you can store the date in a reactive thing its may reactive var, Session or collection.
Then you can return there values from helper to view. And that will auto update your view.
I’m using user’s profile to publish specific data to client.
However the subscribe doesn’t work and it’s killing me.
Although the Tracker.autorun() runs, the server doesn’t run any publish.
Tracker.autorun(function () {
console.log(‘autorun is running');
console.log(Meteor.user()) //to be reactive to user's update
Meteor.subscribe(“Lists”,{
onStop:function() {
console.log('subscribe call back onStop');
},
onReady: function(){
console.log('subscribe call back onReady');
});
console.log(Lists.find().count());
}
Meteor.publish('Lists', function(){
console.log('publish is running’);
var list = Meteor.users.findOne({_id: this.userId}).list;
return Lists.find({_id: {$in: list}});
}
p.s if I type Meteor.subscribe( “Lists”) in Chrome's console line, it works and server run publish normally.
p.s The Lists collection didn't change. Just publishing a different set. Is it the reason?
Thank for your reading.
If you have a close look at the docs for subscribe, you'll find this note in a section about reactive computations:
However, if the next iteration of your run function subscribes to the same record set (same name and parameters), Meteor is smart enough to skip a wasteful unsubscribe/resubscribe.
So because you are always calling subscribe with the same arguments, meteor isn't actually restarting it. The trick is just to pass extra parameters to defeat this "optimization". For example:
Tracker.autorun(function() {
var user = Meteor.user();
var list = user && user.list;
if (!_.isEmpty(list)) {
Meteor.subscribe('Lists', list, function() {
console.log(Lists.find().count());
});
}
});
Here we are extracting the list variable from the user (assuming it's published) and using it as an extra parameter to force the subscription to rerun. If it isn't published, you could just use a random id like this:
Tracker.autorun(function() {
var user = Meteor.user();
Meteor.subscribe('Lists', Random.id(), function() {
console.log(Lists.find().count());
});
});
This should also work but may be a little less efficient because it will fire whenever any property of the user changes.
I think you should run the "console log" after the subscribe and the publish methods return something, if you do somenthing inmediatly is should not work because the server is not returning anything yet, add a callback to the subscription.
Tracker.autorun( function() {
Meteor.subscribe( "List", function() {
console.log( "okok" );
// do your magic
}
};
hope it help, sorry about my english =)
I have the following scenario:
Client side has a button clicking it will execute Meteor.call method on the server-side which will call API and fetch products, During this time I wan't to disable this button + block this method from executing again basically nothing stops you from clicking the button 100x times and server will keep on executing same method again and again.
Few ideas I had in my mind: Use sessions to disable button (Problem: can still using the console Meteor.call and abuse it)
I also looked at Meteor.apply in the docs with wait:true didn't seems to stop from method execution. I honestly not sure how this kind of thing is handled with no hacks.
Client-side:
'click .button-products': function(e){
Meteor.call('getActiveProducts', function(error, results){
if (error)
return Alerts.add(error.reason, 'danger', {autoHide: 5000});
if (results.success)
return Alerts.add('Finished Importing Products Successfully', 'success', {autoHide: 5000});
})
}
Server-side
Meteor.methods({
getActiveProducts: function(){
var user = Meteor.user();
var api = api.forUser(user);
importProducts = function(items){
nextPage = items.pagination.next_page;
items.results.forEach(function(product){
var sameproduct = apiProducts.findOne({listing_id: product.listing_id});
if (sameproduct) {
return;
}
var productExtend = _.extend(product, {userId: Meteor.userId()});
apiProducts.insert(productExtend);
});
};
var products = api.ProductsActive('GET', {includes: 'Images', limit: 1});
importProducts(products);
while (nextPage !== null) {
products = api.ProductsActive('GET', {includes: 'Images', page: nextPage, limit: 1});
importProducts(products);
}
return {success: true};
}
});
From the Meteor docs:
On the server, methods from a given client run one at a time. The N+1th invocation from a client won't start until the Nth invocation returns. However, you can change this by calling this.unblock. This will allow the N+1th invocation to start running in a new fiber.
What this means is that subsequent calls to the method won't actually know that they were made while the first call was still running, because the first call will have already finished running. But you could do something like this:
Meteor.methods({
getActiveProducts: function() {
var currentUser = Meteor.users.findOne(this.userId);
if (currentUser && !currentUser.gettingProducts) {
Meteor.users.update(this.userId, {$set: {gettingProducts: true}});
// let the other calls run, but now they won't get past the if block
this.unblock();
// do your actual method stuff here
Meteor.users.update(this.userId, {$set: {gettingProducts: false}});
}
}
});
Now subsequent calls may run while the first is still running, but they won't run anything inside the if block. Theoretically, if the user sends enough calls, the first call could finish before all of the others have started. But this should at least significantly limit the number of etsy calls that can be initiated by a user. You could adapt this technique to be more robust, such as storing the last time a successful call was initiated and making sure X seconds have passed, or storing the number of times the method has been called in the last hour and limiting that number, etc.
A package I wrote a while back might come in handy for you. Essentially it exposes the Session api on the server side (hence the name), meaning you can do something like ServerSession.set('doingSomethingImportant', true) within the call, and then check this session's value in subsequent calls. The session can only be set on the server, and expires upon connection close (so they could spam calls, but only as fast as they can refresh the page).
In the event of error, you can just reset the session. There shouldn't be any issues related to unexpected errors either because the session will just expire upon connection close. Let me know what you think :)
I am seeing a repeatable issue where a user authenticates ("logs in") with a Meteor server, and then a client subscription that depends on userId is updated (and dependent UI templates reactively update) before Meteor.userId() registers the successful login.
For example, in this code snippet, the assert will throw:
var coll = new Meteor.Collection("test");
if (Meteor.isServer) {
Meteor.publish('mineOrPublic', function () {
// Publish public records and those owned by subscribing user
return coll.find({owner: { $in: [ this.userId, null ]}});
});
}
if (Meteor.isClient) {
var sub = Meteor.subscribe('mineOrPublic');
var cursor = coll.find({});
cursor.observe({
added: function (doc) {
if (doc.owner) {
// This should always be true?!
assert(doc.owner === Meteor.userId());
}
}
});
}
Analogous to the added function above, if I write a template helper that checks Meteor.userId(), it will see a value of null, even when it is invoked with a data context of a document with an owner.
There is apparently a race condition between Meteor collection Pub/Sub and the Account userId update mechanisms. It seems to me that Meteor.userId() should always be updated before any subscriptions update based on a change in this.userId in a server publish function, but for some reason the opposite usually seems to be true (that is, the assert in the code above will usually throw).
The reason I care is because I have packages that depend on obtaining a valid Meteor Authentication token (using Accounts._storedLoginToken()) on the client for use in securing HTTP requests for files stored on the Meteor server. And the authentication token isn't correct until Meteor.userId() is. So the flow of events usually goes something like this:
User logs in
Publish function on server reruns based on the change in this.userId.
Client begins receiving new documents corresponding to the change in userId.
UI Template reactively updates to add DOM elements driven by new documents
Some of the DOM elements are <img> tags with src= values that depend on the data context.
HTTP requests are triggered and ultimately fail with 403 (forbidden) errors because the required authentication cookie hasn't been set yet.
Meteor.userId() finally updates on the client, and code reactively runs to set the authentication cookie
Helpers in the template that depend on a session variable set in the cookie update code are rerun, but the DOM doesn't change, because the URLs in the <img> tags don't change.
Because the DOM doesn't change, the tags don't retry their failed attempts to load the images.
Everything settles down, and the user has to manually reload the page to get their images to appear.
I've come up with two possible approaches to work around this issue:
In the template helper that generates the URL for the <img> tag, always append a dummy query string such as: "?time=" + new Date().getTime(). This causes the DOM to change every time the helper is called and fixes the problem, but it screws-up browser caching and if not coordinated will cause some assets to unnecessarily load multiple times, etc.
In every template helper that touches document data add a test of:
if (this.owner && this.owner !== Meteor.userId()) {
// Perhaps Meteor.loggingIn() could be used above?
// Invalid state, output placeholder
} else {
// Valid state, output proper value for template
}
I really hope someone knows of a less kludgy way to work around this. Alternatively, if consensus arises that this is a bug and Meteor's behavior is incorrect in this respect. I will happily file an issue on Github. I mostly really enjoy working with Meteor, but this is the kind of gritty annoyance that grinds in the gears of "it just works".
Thanks for any and all insights.
After trying lots of things, this variation on the example code in the OP seems to consistently solve the race condition, and I find this an acceptable resolution, unlike my initial attempted workarounds.
I still feel that this kind of logic should be unnecessary and welcome other approaches or opinions on whether Meteor's behavior in the OP sample code is correct or erroneous. If consensus emerges in the comments that Meteor's behavior is wrong, I will create an issue on Github for this.
Thanks for any additional feedback or alternative solutions.
var coll = new Meteor.Collection("test");
if (Meteor.isServer) {
Meteor.publish('mineOrPublic', function (clientUserId) {
if (this.userId === clientUserId) {
// Publish public records and those owned by subscribing user
return coll.find({owner: { $in: [ this.userId, null ]}});
} else {
// Don't return user owned docs unless client sub matches
return coll.find({owner: null});
}
});
}
if (Meteor.isClient) {
Deps.autorun(function () {
// Resubscribe anytime userId changes
var sub = Meteor.subscribe('mineOrPublic', Meteor.userId());
});
var cursor = coll.find({});
cursor.observe({
added: function (doc) {
if (doc.owner) {
// This should always be true?!
assert(doc.owner === Meteor.userId());
}
}
});
}
This code works by giving the server publish function the information it needs to recognize when it is running ahead of the client's own login state, thereby breaking the race condition.
I think this is something that Meteor should do automatically: clients should not see documents based on changes to this.userId in a publish function until after the client Meteor.userId() has been updated.
Do others agree?
I tried with this code that works on server too. In association with FileCollection package.
if (Meteor.isServer) {
CurrentUserId = null;
Meteor.publish(null, function() {
CurrentUserId = this.userId;
});
}
....
OrgFiles.allow({
read: function (userId, file) {
if (CurrentUserId !== file.metadata.owner) {
return false;
} else {
return true;
}
}
...
I'm using the meteor-paginated-subscription package in my app. On the server, my publication looks like this:
Meteor.publish("posts", function(limit) {
return Posts.find({}, {
limit: limit
});
});
And on the client:
this.subscriptionHandle = Meteor.subscribeWithPagination("posts", 10);
Template.post_list.events = {
'click #load_more': function(event, template) {
template.subscriptionHandle.loadNextPage();
}
};
This works well, but I'd like to hide the #load_more button if all the data is loaded on the client, using a helper like this:
Template.post_list.allPostsLoaded = function () {
allPostsLoaded = Posts.find().count() <= this.subscriptionHandle.loaded();
Session.set('allPostsLoaded', allPostsLoaded);
return allPostsLoaded;
};
The problem is that Posts.find().count() is returning the number of documents loaded on the client, not the number available on the server.
I've looked through the Telescope project, which also uses the meteor-paginated-subscription package, and I see code that does what I want to do:
allPostsLoaded: function(){
allPostsLoaded = this.fetch().length < this.loaded();
Session.set('allPostsLoaded', allPostsLoaded);
return allPostsLoaded;
}
But I'm not sure if it's actually working. Porting their code into mine does not work.
Finally, it does look like Mongo supports what I want to do. The docs say that, by default, cursor.count() ignores the effects of limit.
Seems like all the pieces are there, but I'm having trouble putting them together.
None of the answers do what you really want becase none provide solution that is reactive.
This package does exactly what you want and also reactive.
publish-counts
I think you can see the demo: counts-by-room in meteor doc
It can help you publish the counts of your posts at server and get it at client
You can simply write this:
// server: publish the current size of your post collection
Meteor.publish("counts-by-room", function () {
var self = this;
var count = 0;
var initializing = true;
var handle = Posts.find().observeChanges({
added: function (id) {
count++;
if (!initializing)
self.changed("counts", 'postCounts', {count: count});
},
removed: function (id) {
count--;
self.changed("counts", postCounts, {count: count});
}
});
initializing = false;
self.added("counts", 'postCounts', {count: count});
self.ready();
self.onStop(function () {
handle.stop();
});
});
// client: declare collection to hold count object
Counts = new Mongo.Collection("counts");
// client: subscribe to the count for posts
Tracker.autorun(function () {
Meteor.subscribe("postCounts");
});
// client: simply use findOne, you can get the count object
Counts.findOne()
The idea of sub.loaded() is to help you with exactly this problem.
Posts.count() isn't going to return the right thing because, as you've guessed, on the client, Meteor has no way of knowing the real number of posts that live on the server. But what the client knows is how many posts it's tried to load. That's what that .loaded() tells you, and is why the line this.fetch().length < this.loaded() will tell you if there are more posts on the server or not.
What I would do is write a Meteor server side method that retrieves the count like so:
Meteor.methods({
getPostsCount: function () {
return Posts.find().count();
}
});
Then call it on the client, in observe to make it reactive:
function updatePostCount() {
Meteor.call('getPostsCount', function (err, count) {
Session.set('postCount', count);
});
}
Posts.find().observe({
added: updatePostCount,
removed: updatePostCount
});
Although this question is old, I thought I would provide an answer that ended up working for me. I did not create the solution, I found the basis for it here (so credit where credit is due): Discover Meteor
Anyway, in my case I was trying to get "size" of the database from client side, so I can determine when to hide the "load more" -button. I was using template level subscriptions. Oh and for this solution to work, you need to add reactive-var -package. Here is my (in short):
/*on the server we define the method which returns
the number of posts in total in the database*/
if(Meteor.isServer){
Meteor.methods({
postsTotal: function() {
return PostsCollection.find().count();
}
});
}
/*In the client side we first create the reactive variable*/
if(Meteor.isClient){
Template.Posts.onCreated(function() {
var self = this;
self.totalPosts = new ReactiveVar();
});
/*then in my case, when the user clicks the load more -button,
we call the postsTotal-method and set the returned value as
the value of the totalPosts-reactive variable*/
Template.Posts.events({
'click .load-more': function (event, instance){
Meteor.call('postsTotal', function(error, result){
instance.totalPosts.set(result);
});
}
});
}
Hope this helps someone (I recommend checking the link first). For template level subscriptions, I used this as my guide Discover Meteor - template level subscriptions. This was my first stacked-post and I am just learning Meteor, so please have mercy...:D
Ouch this post is old, anyway maybe it will help someone.
I had exactly the same issue. I managed to solve it with 2 simple lines...
Remember the :
handle = Meteor.subscribeWithPagination('posts', 10);
Well I used in client handle.loaded() and Posts.find().count(). Because when they are different it means that all the posts are loaded. So here is my code :
"click #nextPosts":function(event){
event.preventDefault();
handle.loadNextPage();
if(handle.loaded()!=Posts.find().count()){
$("#nextPosts").fadeOut();
}
}
I had the same problem, and using the publish-counts package didn't work with the subs-manager package. I created a package that can set a reactive server-to-client session, and keep the document count in this session. You can find an example here:
https://github.com/auweb/server-session/#getting-document-count-on-the-client-before-limit-is-applied
I'm doing something like this:
On cliente
Template.postCount.posts = function() {
return Posts.find();
};
Then you create a template:
<template name="postCount">
{{posts.count}}
</template>
Then, whatever you want to show the counter: {{> postCount}}
Much easier than any solution i have seen.