Meteor - cursor with limit set isn't reactive - meteor

I don't know if this is bug, but when i specify limit, the cursor on the client isn't reactive if some data that match the query already exists in the client collection.
For instance if have limit: 4 and there is already 1 record that match, than
it returns that one record and when next 3 records, which machtes the query, arrives from the server, the cursor isn't reactive (i am expecting
it will be evaluated again and it will return all those 4 records).
I found it because when i uncomment the line where i am fetching all records, my app works (because that cursor will reflect that new data are available). You can see that query is same, only except that limit.
messages = Messages.find(selector, {sort: {created: -1}, limit: MessagesAPI.LIMIT}).fetch();
//Messages.find(selector, {sort: {created: -1}}).fetch());
// if i uncomment the previous line, it works
More code
getMeteorState: function () {
console.log("zde");
var time = this.getParams().time;
var dir = this.getParams().dir;
//TODO: maybe check time and dir validity or let it crash ?
var ready = Session.get("messages-ready");
var params = {sort: MessagesAPI.sort.NEW, dir: dir == "prev" ? MessagesAPI.dir.PREV : MessagesAPI.dir.NEXT};
if (time) {
var d = new Date();
d.setTime(time);
params.date = d;
}
Meteor.subscribe("messages", params, function () {
console.log("ready");
Session.set("messages-ready", true);
});
var messages = [];
if (ready) {
var selector = {};
if (time && dir) {
selector.created = {};
var cond = (dir == "prev" ? "$lt" : "$gt");
var date = new Date();
date.setTime(time);
selector.created[cond] = date;
}
messages = Messages.find(selector, {sort: {created: -1}, limit: MessagesAPI.LIMIT}).fetch();
//console.log(selector);
// when i uncomment this, it will work
//console.log(Messages.find(selector, {sort: {created: -1}}).fetch());
}
return {
messages: messages
};
},

It is reactive.
If I create a default app and mod it like so
Messages = new Mongo.Collection("messages");
if (Meteor.isClient) {
// counter starts at 0
Session.setDefault("counter", 0);
Template.hello.helpers({
counter: function () {
return Session.get("counter");
},
messages: function() {
var messages = Messages.find({},{sort: {text: -1}, limit: 4}).fetch();
return messages;
}
});
Template.hello.events({
'click button': function () {
Session.set("counter", Session.get("counter") + 1);
Messages.insert({text: Session.get("counter")});
}
});
}
and html
<head>
<title>reactive</title>
</head>
<body>
<h1>Welcome to Meteor!</h1>
{{> hello}}
</body>
<template name="hello">
<button>Click Me</button>
<p>You've pressed the button {{counter}} times.</p>
<div>
{{#each messages}}
{{text}}
{{/each}}
</div>
</template>
No problem. Manual insert via Mongo console reactively updates

Related

How to perform Meteor.call on Session variable change

Here is what I have:
Templates:
<body>
{{> resultSession}}
{{> resultMethod}}
</body>
<template name="resultSession">
<button>Click me</button>
<p>Session.get('length') returned {{returned}}</p>
</template>
<template name="resultMethod">
<p>Meteor.call returned {{returned}}</p>
</template>
Client-side:
Template.resultSession.events({
'click button': function () {
Session.set('length', Math.floor((Math.random() * 20) + 1));
}
});
Template.resultSession.helpers({
returned: function () {
return Session.get('length');
}
});
Template.resultMethod.helpers({
returned: function() {
Meteor.call('returnArray', Session.get('length'), function (err, res) {
return res.length;
});
}
});
Server-side:
Meteor.methods({
'returnArray': function (length) {
var arr = [];
arr[length - 1] = 0;
return arr;
}
});
TL;DR
You can look at code and play with it here http://meteorpad.com/pad/AkBZq4ZFjJuQuzztz/Meteor.call-on-Session-change
As you can see, my method accepts number and returns the array with length equal to number.
The question is how can I make Meteor.call fire each time Session variable changes?
P.S. Values are returned to two different templates on purpose
Your reactive code is working perfectly.
If you put a console.log in the Meteor.call you will see that the correct answer is coming back from the server.
Template.resultMethod.helpers({
returned: function() {
Meteor.call('returnArray', Session.get('length'), function (err, res) {
console.log('it came back ' + res.length);
return res.length;
});
}
});
I have put a Session variable into the return from the server, so now you can see that your reactive code works very simply - no need for complicated autorun stuff.
<template name="resultMethod">
<p>Meteor.call returned {{returned}}</p>
</template>
Then in the resultMethod helper:
Template.resultMethod.helpers({
returned: function() {
Meteor.call('returnArray', Session.get('length'), function (err, res) {
Session.set('fromServer', res.length + '');
});
return Session.get('fromServer');
}
});
Like #saimeunt said, use Tracker.autorun
Templates:
<body>
{{> resultSession}}
{{> resultMethod}}
</body>
<template name="resultSession">
<button>Click me</button>
<p>Session.get('length') returned {{returned}}</p>
</template>
<template name="resultMethod">
<p>Meteor.call returned {{returned}}</p>
</template>
And code:
Template.resultMethod.rendered = function() {
this.autorun(function (){
Meteor.call('returnArray', Session.get('length'), function (err, res) {
Session.set('result', res);
});
});
}
Template.resultSession.helpers({
returned: function () {
return Session.get('length');
}
});
Template.resultMethod.helpers({
returned: function() {
return Session.get('result');
}
});
Autorun inside rendered stops when the template is not rendered
You could simply refactor your code to call the Meteor method on click event ?
Template.resultSession.events({
'click button': function () {
var length = Math.floor((Math.random() * 20) + 1);
Session.set('length', length);
Meteor.call('returnArray', length, function (err, res) {
Session.set('result', res.length);
});
}
});
Template.resultSession.helpers({
returned: function () {
return Session.get('length');
}
});
Template.resultMethod.helpers({
returned: function() {
return Session.get('result');
}
});
You could also use Tracker.autorun to track modifications of your Session variable and rerun arbitrary code.
Tracker.autorun(function(){
var length = Session.get("length");
console.log("length new value =", length);
});

How to increment helper variable count in meteor?

I need to know about to increment helper variable count in meteor.
For example :
<head>
<title>hello</title>
</head>
<body>
<h1>Welcome to Meteor!</h1>
{{> hello}}
</body>
<template name="hello">
<button>Click Me</button>
{{#each arr}}
{{counter}} <!-- How to get the foo index here? --> {{name}}
{{/each}}
</template>
Js Code :
if (Meteor.isClient) {
// counter starts at 0
Template.hello.helpers({
counter: function () {
return Session.get('counter');
}
});
Template.hello.helpers({
arr: function () {
console.log(Session.get('arrres'));
return Session.get('arrres');
}
});
Template.hello.events({
'click button': function () {
Session.set('counter', 0);
Meteor.call('arrfun',10, function (error, res) {
if (!error)
{ arrres = res;
Session.set('arrres', arrres);
console.log(res);
}
else{}
} );
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Meteor.methods({
arrfun:function arrfun(properties, callback)
{
var obj = [];
for(var i = 0 ; i < 10; i++)
{
var obj1 = new Object();
obj1.name = 'abc'+i;
obj.push(obj1);
}
return obj;
}
});
});
}
The above 'arr' contains list of names that present in object.Now can iterate 'arr' it will comes names.
now can we print names as like 1 abc
2 xyz until 'arr' completed.
So how can we print numbers from 1 to 'arr' length before names.
So please suggest me what to do for this.
It's a little hard to interpret your question but I think you want this:
Template.hello.events({
'click button': function(event) {
Session.set('counter', Math.round(Math.random() * 10));
}
});
Template.hello.helpers({
'arr': function() {
var counter = Session.get('counter');
var arr = [];
for(var i = 0; i < counter; i++) {
arr.push(i + 1);
}
return arr;
}
});
And in your hello template:
<ul>
{{#each arr}}
<li>{{this}}</li>
{{/each}}
</ul>
This will generate a random number between 0-10 every time you click the button, and then the template will count up to this random number in sequence.

Manage the reactivity among different visitors

As a beginner, I'm building some tests and trainings.
For now, I have a Monkeys collection that displays a list of monkeys names and ages. Visitors can add or remove monkeys. When a visitor adds a monkey to the list, I wish it would not be visible to the others until they update the list with an update button.
I tried several ways without finding how to reach this purpose.
What is the best way? Or what have I to learn to do it?
Here is my js file(and, of course, all is entirely reactive for now).
//Monkeys collection subscription is in the waitOn function in the router.js page
//--------------------------------------------------------------------
Template.hello.helpers({
monkeys: function(){
var listMonkeys = Monkeys.find({}, {sort: { age: -1, name: 1}});
return listMonkeys;
}
});
//---------------------------------------------------------------------
Template.hello.events({
// insert new monkey
'submit': function (e) {
e.preventDefault();
var name = e.target.new_name.value;
var age = parseInt(e.target.new_age.value);
Monkeys.insert({"name":name,"age":age}, function(error, result) {
if (error) {alert(error.message);}
});
},
//delete this monkey
'click .delete': function(){
var name = this.name;
Monkeys.remove({"_id":this._id}, function(error, result) {
if (result) {alert(name + " has been deleted");}
if (error) {alert(error.message);}
});
},
//update listMonkeys
'click #updatelistMonkeys': function(){
// ... update function
}
});
//--------------------------------------------------------------------------
//There are only some tests in order to understand how works Meteor startup function
Meteor.startup(function () {
var listMonkeys = Monkeys.find({}, {sort: { age: -1, name: 1}}).fetch();
console.log("Meteor.startup: ");
console.log(listMonkeys);//return a empty array
Tracker.autorun(function(){
var listMonkeys = Monkeys.find({}, {sort: { age: -1, name: 1}}).fetch();
console.log("Tracker.autorun: ");
console.log(listMonkeys)//at first, return a empty array, immediately after,return the filled array
})
});
Here is my html template:
<template name="hello">
<h2>Monkeys forever!</h2>
<button id = "updatelistMonkeys"><img src="images/update-contacts-icon.png" style= "width: 2em;"></button>
{{#each monkeys}}
<p class = "listMonkeys">Name: {{name}} - age: {{age}} <button class="delete">X</button></p>
{{/each}}
<form>
<legend ><b>Add a new Monkey: </b> </legend>
<input type="text" name="new_name">
<input type="number" name="new_age">
<button type="submit">submit</button>
</form>
</template>
You can disable reactivity within your helper by making two modifications, include the reactive: false option and add .fetch() to the end, to force the results to be retuned, not a cursor. Try this:
monkeys: function() {
return Monkeys.find({}, {sort: {age: -1, name: 1}, reactive: false}).fetch();
}
Got this work around from a github issue:
https://github.com/meteor/meteor/issues/771
You can load your data from a server method, this solves some problems you have right now but you still need to dev a notification system for your update button to rerun the server method.
// server/methods.js
Meteor.methods({
getMonkeys: function () {
return Monkeys.find({}, {sort: { age: -1, name: 1}}).fetch();
}
})
//tpl helper
Template.hello.helpers({
monkeys: function(){
Meteor.call("getMonkeys", function(err,monkeys){
if(err){
//do smth with the error
}
return monkeys;
})
}
})
# Brian Shambien: it still reactive. You can see there ==> monkeys.meteor.com
I suppose it's because the variable is in a reactive context and re-evaluated each time it changes.
Template.hello.helpers({
monkeys: function(){
var listMonkeys = Monkeys.find({}, {sort: {age: -1, name: 1}, reactive: false}).fetch();
return listMonkeys;
}
});

Meteor.js calling a template.helpers function vs global variable

I am using Reactive-table to display paginated data in my meteor.js app as shown below, yet data displayed in Reactive-table is dependent on on specific user event (Selecting client, project, date range and clicking on the submit button). So I was wondering if it is possible to trigger template.helpers >> myCollection function from the 'submit form' event? OR is it better to define a global variable to store data returned from user query based on the user (client, project, date range selection) then make this global variable the return from the myCollection function?
I have tried researching how to call .helpers function from an template.events event but couldn't find any information. So any help on which approach is better and if calling the .events function is better then how to do that, will be highly appreciated. Thanks.
Below is the code I have in my app:
Template.detailedreport.rendered = function() {
Session.set("dreport_customer", "");
Session.set("dreport_project", "");
Session.set("dreport_startDate", new Date());
Session.set("dreport_endDate", new Date());
$('.set-start-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-end-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-start-date').on("dp.change",function (e) {
Session.set("dreport_startDate", $('.set-start-date').data('DateTimePicker').getDate().toLocaleString());
});
$('.set-end-date').on("dp.change",function (e) {
Session.set("dreport_endDate", $('.set-end-date').data('DateTimePicker').getDate().toLocaleString());
});
};
Template.detailedreport.helpers({
customerslist: function() {
return Customers.find({}, {sort:{name: -1}});
},
projectslist: function() {
return Projects.find({customerid: Session.get("dreport_customer")}, {sort:{title: -1}});
},
myCollection: function () {
var now = Session.get("dreport_startDate");
var then = Session.get("dreport_endDate");
var custID = Session.get("dreport_customer");
var projID = Session.get("dreport_project");
Meteor.call('logSummary', now, then, projID, custID, function(error, data){
if(error)
return alert(error.reason);
return data;
});
}
},
settings: function () {
return {
rowsPerPage: 10,
showFilter: true,
showColumnToggles: false,
fields: [
{ key: '0._id.day', label: 'Day' },
{ key: '0.totalhours', label: 'Hours Spent'}
]
};
}
});
Template.detailedreport.events({
'submit form': function(e) {
e.preventDefault();
var now = $('.set-start-date').data('DateTimePicker').getDate().toLocaleString();
var then = $('.set-end-date').data('DateTimePicker').getDate().toLocaleString();
var custID = $(e.target).find('[name=customer]').val();
var projID = $(e.target).find('[name=project]').val();
//Here is the problem as I am not sure how to refresh myCollection function in .helpers
},
'change #customer': function(e){
Session.set("dreport_project", "");
Session.set("dreport_customer", e.currentTarget.value);
},
'change #project': function(e){
Session.set("dreport_project", e.currentTarget.value);
}
});
Template:
<div>
{{> reactiveTable class="table table-bordered table-hover" collection=myCollection settings=settings}}
</div>
Server:
Meteor.methods({
logSummary: function(startDate, endDate, projid, custid){
//Left without filtering based on date, proj, cust for testing only...
return Storylog.find({});
}
});
Template helpers are reactive, meaning that they will be recomputed if their dependencies change. So all you need to do is update their dependencies and then the myCollection helper will be recomputed.
Replace your comment // Here is the problem... with:
Session.set('dreport_endDate', then);
Session.set('dreport_startDate', now);
Session.set('dreport_project', projID);
Session.set('dreport_customer', custID);

MeteorJS: Collection.find fires multiple times instead of once

I have an app that when you select an industry from a drop down list a collection is updated where the attribute equals the selected industry.
JavaScript:
Template.selector.events({
'click div.select-block ul.dropdown-menu li': function(e) {
var selectedIndex = $(e.currentTarget).attr("rel");
var val = $('select#industryPicker option:eq(' + selectedIndex + ')').attr('value');
var oldVal = Session.get('currentIndustryOnet');
if(val != oldVal) {
Session.set('jobsLoaded', false);
Session.set('currentIndustryOnet', val);
Meteor.call('countByOnet', val, function(error, results){
if(results > 0) {
Session.set('jobsLoaded', true);
} else {
getJobsByIndustry(val);
}
});
}
}
});
var getJobsByIndustry = function(onet) {
if(typeof(onet) === "undefined")
alert("Must include an Onet code");
var params = "onet=" + onet + "&cn=100&rs=1&re=500";
return getJobs(params, onet);
}
var getJobs = function(params, onet) {
Meteor.call('retrieveJobs', params, function(error, results){
$('job', results.content).each(function(){
var jvid = $(this).find('jvid').text();
var job = Jobs.findOne({jvid: jvid});
if(!job) {
options = {}
options.title = $(this).find('title').text();
options.company = $(this).find('company').text();
options.address = $(this).find('location').text();
options.jvid = jvid;
options.onet = onet;
options.url = $(this).find('url').text();
options.dateacquired = $(this).find('dateacquired').text();
var id = createJob(options);
console.log("Job Created: " + id);
}
});
Session.set('jobsLoaded', true);
});
}
Template.list.events({
'click div.select-block ul.dropdown-menu li': function(e){
var selectedIndex = $(e.currentTarget).attr("rel");
var val = $('select#perPage option:eq(' + selectedIndex + ')').attr('value');
var oldVal = Session.get('perPage');
if(val != oldVal) {
Session.set('perPage', val);
Pagination.perPage(val);
}
}
});
Template.list.jobs = function() {
var jobs;
if(Session.get('currentIndustryOnet')) {
jobs = Jobs.find({onet: Session.get('currentIndustryOnet')}).fetch();
var addresses = _.chain(jobs)
.countBy('address')
.pairs()
.sortBy(function(j) {return -j[1];})
.map(function(j) {return j[0];})
.first(100)
.value();
gmaps.clearMap();
$.each(_.uniq(addresses), function(k, v){
var addr = v.split(', ');
Meteor.call('getCity', addr[0].toUpperCase(), addr[1], function(error, city){
if(city) {
var opts = {};
opts.lng = city.loc[1];
opts.lat = city.loc[0];
opts.population = city.pop;
gmaps.addMarker(opts);
}
});
})
return Pagination.collection(jobs);
} else {
jobs = Jobs.find()
Session.set('jobCount', jobs.count());
return Pagination.collection(jobs.fetch());
}
}
In Template.list.jobs if you console.log(addresses), it is called 4 different times. The browser console looks like this:
(2) 100
(2) 100
Any reason why this would fire multiple times?
As #musically_ut said it might be because of your session data.
Basically you must make the difference between reactive datasources and non reactive datasources.
Non reactive are standard javascript, nothing fancy.
The reactive ones however are monitored by Meteor and when one is updated (insert, update, delete, you name it), Meteor is going to execute again all parts which uses this datasource. Default reactive datasources are: collections and sessions. You can also create yours.
So when you update your session attribute, it is going to execute again all helper's methods which are using this datasource.
About the rendering, pages were rendered again in Meteor < 0.8, now with Blaze it is not the case anymore.
Here is a quick example for a better understanding:
The template first
<head>
<title>test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<h1>{{getSession}}</h1>
<h1>{{getNonReactiveSession}}</h1>
<h1>{{getCollection}}</h1>
<input type="button" name="session" value="Session" />
<input type="button" name="collection" value="Collection" />
</template>
And the client code
if (Meteor.isClient) {
CollectionWhatever = new Meteor.Collection;
Template.hello.events({
'click input[name="session"]': function () {
Session.set('date', new Date());
},
'click input[name="collection"]': function () {
CollectionWhatever.insert({});
}
});
Template.hello.getSession = function () {
console.log('getSession');
return Session.get('date');
};
Template.hello.getNonReactiveSession = function () {
console.log('getNonReactiveSession');
var sessionVal = null;
new Deps.nonreactive(function () {
sessionVal = Session.get('date');
});
return sessionVal;
};
Template.hello.getCollection = function () {
console.log('getCollection');
return CollectionWhatever.find().count();
};
Template.hello.rendered = function () {
console.log('rendered');
}
}
If you click on a button it is going to update a datasource and the helper method which is using this datasource will be executed again.
Except for the non reactive session, with Deps.nonreactive you can make Meteor ignore the updates.
Do not hesitate to add logs to your app!
You can read:
Reactivity
Dependencies

Resources