Meteor: publish dynamically requested range of items - javascript

I have huge collection of over 5000+ records. I want to be able to view records 10 at a time. How can I dynamically publish the data that way?
I've tried this so far:
My server.js file :
Meteor.methods({
publishSongs : function (first, last) {
Meteor.publish('adminSongs', function() {
return Songs.find({}, {
skip : first,
limit : last,
sort : {
date : -1
}
});
});
}
});
My client.jsfile :
Template.admin.events({
'click #previous' : function() {
updateSession(-10);
publishSong();
},
'click #next' : function() {
updateSession(10);
publishSong();
}
});
Template.admin.onCreated(function() {
Session.setDefault('limit', {
first : 0,
last : 10
});
publishSong()
})
function publishSong() {
Meteor.call(
'publishSong',
Session.get('limit').first,
Session.get('limit').last
);
}
function updateSession(value) {
Session.set('limit', {
first: Session.get('limit').first + value,
last: Session.get('limit').last + value,
});
}
The server is printing this error message:
Ignoring duplicate publish named 'adminSongs'
It seems like I'm using publications wrong and could use some guidance.

It doesn't look like you're never updating your Session.get('limit'). You'll need to update then you press next/previous otherwise you're always going to get the same records. You'll also need to change the way you're doing publications:
Template.admin.events({
'click #previous' : function() {
updateSession(-10);
},
'click #next' : function() {
updateSession(10);
}
});
Template.admin.onCreated(function() {
Session.setDefault('limit', {
first : 0,
last : 10
});
Template.instance().autorun( function() {
Template.instance().subscribe('adminSongs', Session.get('limit').first, Session.get('limit').last);
});
});
function updateSession(value) {
Session.set('limit', {
first: Session.get('limit').first + value,
last: Session.get('limit').last + value,
});
}
I'm assuming based on your code that you already have a helper defined to return the available songs. The code above makes it so that you have one subscription, and that subscription will update any time your session variable changes.
Your server code will also need to be updated:
Meteor.publish('adminSongs', function(first, last) {
return Songs.find({}, {
skip : first,
limit : last,
sort : {
date : -1
}
});
});
Can be outside of a Meteor.method.

Related

Change the way a collection is sorted through the client

I have a collection that is shown on a page :
Template.body.helpers({
chansonsFutures(){
return Chansons.find({
"playedStatus":false
},{
sort : { score:-1 }
});
},
});
And I would like the user to be able to change the way the collection is sorted. In the example it is sorted by score, but how would I go about sorted by something else?
I have a select in place :
<select id="ordre">
<option value="shuffle">Aléatoire</option>
<option value="vote">Vote</option>
<option value="lastAdded">Dernier Ajout</option>
</select>
But if I put an if condition in Template.body.helpers, meteor tells my application has errors and is waiting for file changes. If I put the condition in Template.body.events and link it to the change of my select, it also gives me errors.
How should I proceed?
edit : to answer #Justinas, here is how I put the condition in code.
My first try was straight in the helper :
Template.body.helpers({
if(document.getElementById("ordre").value == "lastAdded"){
chansonsFutures(){
return Chansons.find({
"playedStatus":false
},{
sort : { lastAdded:-1 }
});
},
};
else if (other value for the select) {
other stuff happens
};
});
And the other way was like this :
Template.body.events({
'change #ordre'(event){
event.preventDefault();
chansonsFutures(){
return Chansons.find({
"playedStatus":false
},{
sort : { document.getElementById("ordre").value:-1 }
});
},
},
});
Here it is what you need to add in your js fiile :
By defalult it will sot by 'score' and on changing select value it will change accordingly.
Template.body.onCreated(function() {
this.sortBy = new ReactiveVar('score');
})
Template.body.helpers({
chansonsFutures : function(){
var sortParam = Template.instance().sortBy.get();
return Chansons.find({
"playedStatus":false
},{
sort : { sortParam:-1 }
});
},
});
Template.body.events({
'change #ordre':function (event,templateInstance) {
var sortSelected = $('#ordre option:selelcted').val();
templateInstance.sortBy.set(sortSelected)
}
})

Up and Downvote button

I try to achieve an up or downvote button where a user is able to vote just 1 time up and 1 time down. If you already have upvoted something it should be possible to remove that with another click on the upvote button, but i dont know what is missing for this. My code looks like the following. I guess i have to implement something with a true of false statement but i tried some things and nothing worked. I would appreciate your help!
Template.postArgument.events({
'click':function() {
Session.set('selected_argument', this._id);
},
'click .yes':function() {
if(Meteor.user()) {
var postId = Arguments.findOne({_id:this._id})
console.log(postId);
if($.inArray(Meteor.userId(), postId.votedUp) !==-1) {
return "Voted";
} else {
var argumentId = Session.get('selected_argument');
Arguments.update(argumentId, {$inc: {'score': 1 }});
Arguments.update(argumentId, {$addToSet: {votedUp: Meteor.userId()}});
}
}
}});
Your general approach is correct however you don't need the Session variable at all or even the first click handler. And you don't need to return anything at all from the function.
Template.postArgument.events({
'click .yes': function(){
if ( Meteor.user() ) {
var post = Arguments.findOne({_id:this._id});
if ( $.inArray(Meteor.userId(), post.votedUp) === -1 ) {
Arguments.update(this._id, {
$inc: { score: 1 },
$addToSet: { votedUp: Meteor.userId() }
});
} else {
Arguments.update(this._id, {
$inc: { score: -1 },
$pull: { votedUp: Meteor.userId() }
});
}
}
}
});
You can start simple by checking for the existence of the user in the upvotes and downvotes and increment/decrement accordingly then add the user to the sets.
Meteor.methods({
'downvote post': function (postId) {
check(postId, String);
let post = Posts.findOne(postId);
Posts.update(postId, post.downvoters.indexOf(this.userId !== -1) ? {
$inc: { downvotes: -1 }, // remove this user's downvote.
$pull: { downvoters: this.userId } // remove this user from downvoters
} : {
$inc: { downvotes: 1 }, // add this user's downvote
$addToSet: { downvoters: this.userId } // add this user to downvoters.
});
}
});

How can I update a complex knockout observable programatically?

I'm using durandal/requirejs/knockout here.
I'm also using the coderenaissance plugin for mapping (ko.viewmodel.updateFromModel(zitem, data).)
I'm getting the following data from my ajax call which I'm mapping into my zitem observable.
{
"itemNumber" : "ABATAH000",
"effectiveDate" : "2015-11-03T15:30:05.7118023-05:00",
"expiryDate" : "2015-05-03T15:30:05.7118023-04:00",
"minimumPremium" : 25,
"zSubItems" : [{
"zSubItemName" : "Mine",
"unitDistance" : 100000,
"zSubSubItems" : [{
"zSubSubItemName" : "CoverageA",
"zSubSubItemPremium" : 100.0,
"id" : 0
}
],
"id" : 1
}
],
"id" : 0
}
And here is the viewmodel I'm using:
define(['plugins/http', 'durandal/app', 'knockout', 'services/datacontext'],
function (http, app, ko, datacontext) {
var zitem = ko.observable();
var activate = function () {
//This is just a wrapper around an ajax call.
return datacontext.getPolicy("value")
.then(function(data) {
ko.viewmodel.updateFromModel(zitem, data);
});
};
var updateMinimumPremium = function (thisItem) {
//This doesn't work
zitem.minimumPremium(thisItem.minimumPremium + 1);
};
return {
displayName: 'zitem example',
zitem: zitem,
updateMinimumPremium: updateMinimumPremium,
activate: activate
};
});
I'm binding the updateMinimumPremium to a click on a button at the same level as the minimumPremium element.
<button data-bind="click: $parent.updateMinimumPremium">Add 1</button>
How can I update [minimumPremium] or [zSubSubItemPremium] programatically?
"minimumPremium" would be observable
zitem.minimumPremium(thisItem.minimumPremium() + 1);
Your zitem is observable as well, so try this:
zitem().minimumPremium(thisItem.minimumPremium + 1);
In real application don't forget to check the value of zitem() call - it can be uninitialized.

Subscribing the collection (Meteor)

I have some specific problem.
I use MeteorJS and installed yogiben:admin. I tried to build some schema, but I have an error after updating something.
I want to add that I have subpages in page, maybe that's the problem?
That's what I get after adding items to my invoice:
http://s7.postimg.org/l0q52l27v/error.png
As I can see in the picture, the problem is with some modifier and with "After.Update.sum". I use function that use "sum".
In my "server/collections/invoices_item.js"
I have:
InvoicesItem.after.update(function(userId, doc, fieldNames, modifier, options) {
var sum = 0; InvoicesItem.find({ invoiceId: doc.invoiceId }).map(function(item) { sum += item.amount; }); Invoices.update({ _id: doc.invoiceId }, { $set: { totalAmount: sum }});
});
Than I saw that problem could be with "totalAmount:sum". I use Chrome, so I tried "console.log()" to see if the page takes my collection.
And it doesn't.
I use Chrome, so I tried to see what the console will give me. I have something like this: http://s4.postimg.org/rusm4wx9p/fakturka.png
I did sth like that in my code on server side:
Meteor.publish("fakturka", function(invoiceId) {
return Invoices.find({_id:invoiceId,ownerId:this.userId}, {});
});
And did that on client side:
this.InvoicesNewInsertController = RouteController.extend({
template: "InvoicesNew",
yieldTemplates: {
'InvoicesNewInsert': { to: 'InvoicesNewSubcontent'}
},
onBeforeAction: function() {
/*BEFORE_FUNCTION*/
this.next();
},
action: function() {
if(this.isReady()) { this.render(); } else { this.render("InvoicesNew"); this.render("loading", { to: "InvoicesNewSubcontent" });}
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
Meteor.subscribe("invoices_item"),
Meteor.subscribe("invoiceeeee"),
Meteor.subscribe("customers"),
Meteor.subscribe("fakturka", this.params.invoiceId),
Meteor.subscribe("invoices_item_empty_faktura"),
Meteor.subscribe("invoices_itemss_faktura", this.params.invoiceId)
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
params: this.params || {},
invoices_item: InvoicesItem.find({}, {}),
invoiceeeee: Invoices.find({}, {}),
customers: Customers.find({}, {}),
fakturka: Invoices.findOne({_id:this.params.invoiceId}, {}),
invoices_item_empty_faktura: InvoicesItem.findOne({_id:null}, {}),
invoices_itemss_faktura: InvoicesItem.find({invoiceId:this.params.invoiceId}, {})
};
/*DATA_FUNCTION*/
},
onAfterAction: function() {
}
});
I'm sorry for so much code, but I really want to solve that problem and I want to give so much info as I could. Please, help me to solve my problem.
After removing that code from: both/collections/invoices.js
Schemas={};
Schemas.Invoicess = new SimpleSchema({
invoiceNumber:{
type:Number
},
date_issued:{
type:Date
},
date_due:{
type:Date
},
customerId:{
type:String
},
totalAmount:{
type:String
}
});
Invoices.attachSchema(Schemas.Invoicess);
"fakturka" is visible. After adding that code - "fakturka" in undefined.

How do i Meteor Reset client subscription

i have subscribe and publish like this :
publish.js :
Meteor.publish('ProductWithSkipAndLimit', function(skip,limit){
return Product.find({}, {
sort: {
createdAt: 1
},
skip: skip,
limit: limit
});
});
subscribe.js :
Meteor.subscribe('ProductWithSkipAndLimit',0,10);
and it will return to client 10 products from 0 sort by createdAt.
Nah i have an event click like this :
'click' : function(e){
e.preventDefault();
Meteor.subscribe('ProductWithSkipAndLimit',10,10);
}
I want to get 10 more products. okay i get that products, but 10 products not reset. so on client i have 20 products.
how i can reset client subscription? so client only have 10 products every subscribe.
Meteor.subscribe:
Subscribe to a record set. Returns a handle that provides stop() and ready() methods.
You need to take handle of Meteor.subscribe
subscription = Meteor.subscribe('ProductWithSkipAndLimit',10,10);
And in events object :
var subscription;
Template.NAME.events({
'click' : function(e){
e.preventDefault();
subscription && subscription.stop();
subscription = Meteor.subscribe('ProductWithSkipAndLimit',10,10);
}
})
I think, in click event you have to set Session variable Session.set('more', true);
On client:
Deps.autorun(function() {
if(Session.get('more')) {
Meteor.subscribe('ProductWithSkipAndLimit',10,10);
Session.set('more', false);
}
});
Or some logic to set current position in collection (10, 20, etc.)
You asked about subscription resetting, but it looks like there is no need to do it manually in your case.
You can subscribe within Tracker.autorun and pass reactive values as subscription parameters.
Then on each skip/limit session variable change the subscription will be reset automatically.
From Meteor official documentation:
If you call Meteor.subscribe within a reactive computation, for example using Tracker.autorun, the subscription will automatically be cancelled when the computation is invalidated or stopped; it's not necessary to call stop on subscriptions made from inside autorun.
Here is working example (METEOR#1.1.0.2):
Items = new Meteor.Collection("items");
if(Meteor.isClient) {
Tracker.autorun(function() {
Meteor.subscribe("items", Session.get("skip"), Session.get("limit"));
});
Template.main.helpers({
items: function() {
return Items.find({});
}
});
Template.main.events({
'click #next' : function(e){
e.preventDefault();
var skip = Session.get("skip");
Session.set("skip", skip + Session.get("limit"));
},
'click #prev' : function(e){
e.preventDefault();
var skip = Session.get("skip");
Session.set("skip", skip - Session.get("limit"));
}
});
Meteor.startup(function() {
Session.set("skip", 0);
Session.set("limit", 10);
});
}
if(Meteor.isServer) {
if (Items.find({}).fetch().length < 100) {
_.times(100, function(n) {
Items.insert({
name: String(n),
createdAt: new Date()
});
});
}
Meteor.publish("items", function(skip, limit) {
return Items.find({}, { limit: limit, skip: skip, sort: { createdAt: 1} });
});
}
template
<template name="main">
<header>
<h1>Items</h1>
<nav>
<button id="prev">prev</button>
<button id="next">next</button>
</nav>
</header>
<ul>
{{#each items}}
<li>{{name}}</li>
{{/each}}
</ul>
</template>
P.S. Don't forget to remove "autopublish" package

Categories

Resources