Meteor Reactive Session: Not Working (Why?) - meteor

I'm having trouble with reactive Sessions in Meteor.js.
Demo: Meteor Pad
Template.rows.helpers({
'rows': function () {
return Session.get('rows'); // data set in Session
}
});
Template.count.events({
'click .mdl-radio__button': function (e) {
// target represents a number of selected rows (1, 2, 5, or 10)
var value = $(e.currentTarget).val();
Session.set('limit', value);
},
'click #reset': function () {
Session.set('limit', 0);
Session.set('rows', null);
},
'click #run': function () {
// should only get rows when run() is pressed
Session.set('rows', currentItems);
}
});
Users should be able to select a new number of collections to receive, controlled by the limit. However, I keep getting the following error:
Error: Match error: Failed Match.OneOf or Match.Optional validation
Any ideas why? Can someone show me a working MeteorPad demo?

I'm having trouble with your meteorpad. But your problem isn't Session. The problem is your usage of Tracker.autorun. You should read the docs on that.
You are assuming that Tracker.autorun(getItems) returns what getItems returns. That's not the case tough. You'll need to set currentItems inside the autorun (in your case getItems).
getItems = function () {
if (Session.get('limit') > 0) {
currentItems = Items
.find({}, {limit: Session.get('limit')})
.map(function (item, index) {
item.index = index + 1;
return item;
});
} else {
currentItems = null;
}
};

Finally figured it out. Apparently Session creates a string, so that Session.set('limit', 1) sets the limit to "1". Of course, strings can be processed in a Mongo collection request.
The solution was using {limit: parseInt(Session.get('limit')}.

Related

Meteor subscription is not stopping

I've got what should be a relatively simple issue. I set a session, then a subscribe to a collection using the string stored in the session. But when that session changes, I need to clear the subscription data and start again.
My code is as follows:
let subscriptionReady;
let filteredResults = [];
let rawResults = [];
let county = Session.get('county');
let type = Session.get('type');
This is mostly just prep work to create some empty objects to populate later. This all gets set on a click event. After we set these placeholder objects we go and subscribe by those sessions:
if (county && !type) {
return function() {
if (subscriptionReady) {
subscriptionReady.stop();
}
filteredResults = [];
rawResults = [];
subscriptionReady = Meteor.subscribe('resourcesearch', county, {
onReady: () => {
rawResults = resourceCollection.find({}, { sort: {score: -1} }).fetch();
rawResults.forEach((result) => {
if (result.score) {
filteredResults.push(result);
}
});
}
});
}
At the third line I run a check to see if subscriptionReady exists, then it will have the stop method available. So then I run it. But, it doesn't actually stop anything.
What am I missing?
After trial and error, I've got it solved. The issue was the placement of the stop call. I no longer have to check if subscriptionReady exists, instead I stop the subscription inside of the onReady method:
return function() {
filteredResults = [];
rawResults = [];
subscriptionReady = Meteor.subscribe('resourcesearch', county, {
onReady: () => {
rawResults = resourceCollection.find({}, { sort: {score: -1} }).fetch();
rawResults.forEach((result) => {
if (result.score) {
filteredResults.push(result);
}
});
subscriptionReady.stop();
}
});
It's .stop() not .stop docs
Also you can probably avoid your filtering loop by including score in your query. Are you looking for documents where the score key exists {score: {$exists: true}} or just where it is non zero {$score: {$ne: 0}}?
Also you shouldn't need to clear the subscription and start again. If you make your subscription parameter resourcesearch a reactive data source then the subscription will automatically update to give you the documents you need. Starting/stopping a subscription in response to a search would be an anti-pattern.

Non-reactive and reactive data in Meteor (same helper)

I've got one view displaying some pictures published by users with some data (let's image Instagram).
I already have these pictures as non-reactive data (otherwise you could see many updates) but these images have one button to like the picture. If I have this as non-reactive data I can't see when I click on "Like" the filled heart (I need to refresh).
This is my subscribe function:
this.subscribe('food', () => [{
limit: parseInt(this.getReactively('perPage')),
//skip: parseInt((this.getReactively('page') - 1) * this.perPage),
sort: this.getReactively('sort')
}, this.getReactively('filters'), this.getReactively('searchText'), this.getReactively('user.following')
]);
And this is my helper:
food() {
const food = Food.find({}, {reactive: true}, {
sort: this.sort
}).fetch().map(food => {
const owner = Meteor.users.findOne(food.owner, {fields: {username: 1, avatarS: 1, following: 1}});
food.avatarS = owner && owner.avatarS;
food.username = owner && owner.username;
if (food.likes.indexOf(Meteor.userId()) == -1) {
// user did not like this plate
food.liked = false;
} else {
// user liked this plate
food.liked = true;
}
return food;
});
}
Is possible to have a non-reactive model but with some reactive properties on it?
I'm using Angular 1.X with TS btw
Thanks in advance!
PS: is it normal that this works as non-reactive when I change reactive to true?
Modification to your code:
//console.log(food.likes);
this.subscribe('reactiveFoodData', {ownerId: food.owner, userId: Meteor.userId()}).subscribe(()=>{
console.log(this.user);
});
// THIS IS THE PUBLISH METHOD LOCATED IN THE SERVER SIDE:
Meteor.publish('reactiveFoodData', function(params: {ownerId:string, userId:string) {
const owner = Meteor.users.findOne(params.ownerId);
if (!owner) {
throw new Meteor.Error('404', 'Owner does not exist');
}
let result = {};
result.avatarS = owner.avatarS;
result.username = owner.username;
const food = Food.find({});
result.liked = !(food.likes.indexOf(params.userId) == -1);
return result;
});
You have few problems:
1. The reactive flag is true by default, you do not need to set it.
2. The function find is accepting only two arguments, not 3.
Should be:
const food = Food.find({}, {reactive: true, sort: this.sort})
If you need some, subset of data to be reactive only (from some collection). You could create a specific Method (which udpates only "likes").
https://guide.meteor.com/methods.html
UPDATE:
Here is how you write a method with return parameter (check two examples, with Future and without):
How to invoke a function in Meteor.methods and return the value
UPDATE2:
You have lost reactivity when you used fetch(). Because you moved from reactive cursor to just simple array over which you map values. Do not expect reactivity after fetch(). If you want fetch or do not want to use Cursors, you could wrap the find inside Tracker.autorun(()=>{}) or utilize publish/subscribe.
Note: But be careful, if you somehow manage to get "empty" cursor in find(), your Tracker.autorun will stop react reactively. Autorun works only if it has something to watch over.
The main point with method, is that if you want to have one time non-reactive action for something. You define the method on server:
Meteor.methods({
myMethod: ()=> {
return "hello";
}
});
And you can call it from client with:
Meteor.call('myMethod', (error, result) => {
console.log(result); // "hello"
});
Instead of working with pure collections. You could start using publish/subscribe. On server you publish 'likes' and on client you just listens to this new reactive view. E.g.,
Meteor.publish('likes', (options: {owner: string, likes: Array<any>}) => {
let result: any = {}
const owner = Meteor.users.findOne(options.owner, username: 1, avatarS: 1, following: 1}});
result.avatarS = options.owner && options.owner.avatarS;
result.username = options.owner && options.owner.username;
result.liked = !(options.likes.indexOf(Meteor.userId()) == -1)
return result;
});
On client side: Meteor.subscibe('likes', {food.owner, food.likes}).subscribe(()=>{});
This is just off the top of my head.
Have you tried looking at Tracker ? https://docs.meteor.com/api/tracker.html
But more specifically the method Tracker.nonreactive
https://docs.meteor.com/api/tracker.html#Tracker-nonreactive

Meteor publication error - Publish function returned an array of non-Cursors

I have this publication
Meteor.publish('temsInThisCompetition', function (id) {
var teams = [];
return Competitions.find(id).fetch().map(function (doc) {
for(var item in doc.teams){
teams.push(Teams.find(item));
}
return teams;
});
});
But I am getting this error
Exception from sub temsInThisCompetition id kDPuEbc9dtWn2tfT3 Error: Publish function returned an array of non-Cursors
This solved the problem for me
Meteor.publish('teamsInThisCompetition', function (id) {
var competition = Competitions.findOne(id);
return Teams.find({_id:{$in:competition.teams}});
});
Since Meteor is asynchronous, your initial code doesn't work because the team array is returned before the for loop is completed.
If you need a for loop in an async environment, you could use a callback function, like this:
function getTeams(id, callback) {
Competitions.find(id).fetch().map(function (doc) {
var teams = [];
for(var item in doc.teams){
teams.push(Teams.find(item));
if(teams.length==doc.teams.length) {
callback(teams);
}
}
});
}
Meteor.publish('temsInThisCompetition', function (id) {
getTeams(id, function(teams) {
return teams;
});
});
Collection.find() without a callback and Collection.findOne() are synchronous, that's why the code in your answer doesn't return an empty set.

Using of Session Issues in Meteor JS?

I need to know about the Sessions.Actually we using default sessions like session.set(key,value) and session.get(key). In this default session are cleared some cases like refresh and etc.
First i am using meteor add u2622:persistent-session Pkg.Use of this pkg gets one error i.e "Uncaught Error: Meteor does not currently support objects other than ObjectID as ids".
To overcome those problems to use amplify Sessions. But did one sample to using amplify Sessions as shown below code :
Js Code :
Messages = new Meteor.Collection("messages");
if (Meteor.isClient) {
var AmplifiedSession = _.extend({}, Session, {
keys: _.object(_.map(amplify.store(), function (value, key) {
return [key, JSON.stringify(value)];
})),
set: function (key, value) {
Session.set.apply(this, arguments);
amplify.store(key, value);
}
});
// counter starts at 0
Session.setDefault('counter', 0);
AmplifiedSession.set('no', 1);
Template.hello.helpers({
counter: function () {
return Session.get('counter');
}
});
Template.hello.helpers({
no: function () {
return AmplifiedSession.get('no');
}
});
Template.hello.events({
'click button': function () {
// increment the counter when button is clicked
console.log("Btn Clicked");
Session.set('counter', Session.get('counter') + 1);
AmplifiedSession.set('no',AmplifiedSession.get('no') + 1);
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
Even not working.amplify Sessions also cleared at the time of Refresh.I didn't get any idea about this.So please suggest me what to do for this.
Thanks in Advance.
Try this package on atmosphere and let me know if it helped.
meteor add u2622:persistent-session
In this particular example, on every page load, you are running AmplifiedSession.set('no', 1);, therefore setting 'no' to be 1. This is why on page refreshes, 'no' is getting set to 1. Remove this line, and then change this line AmplifiedSession.set('no',AmplifiedSession.get('no') + 1); to set the value of 'no' if it does not exist.

SQLite with Cordova: Unable to initialize database on other pages

I'm playing around SQLite in Cordova as part of an upskilling process for work and I'm hitting a brick wall. The various articles I've read around initializing the SQLite plugin from Chris Brody is to always call it in after device ready, but all examples are around the index page. What if I need to populate data on the products.html page, without also calling all other initialization calls to the database?
What I mean is, given the following JS file, called core.js:
var db,
app = {
// Application Constructor
initialize: function() {
this.bindEvents();
},
// Bind Event Listeners
//
// Bind any events that are required on startup. Common events are:
// 'load', 'deviceready', 'offline', and 'online'.
bindEvents: function() {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
// deviceready Event Handler
//
// The scope of 'this' is the event. In order to call the 'receivedEvent'
// function, we must explicitly call 'app.receivedEvent(...);'
onDeviceReady: function () {
app.receivedEvent('deviceready');
},
// Update DOM on a Received Event
receivedEvent: function (id) {
app.initdb();
console.log('Received Event: ' + id);
},
initdb: function () {
try {
db = window.sqlitePlugin.openDatabase({ name: 'meatblock.db' });
if (!db) {
console.error('Database unable to initialize, it either does not exist or is null');
return false;
}
else {
return true;
}
}
catch (err) {
console.error('Database initialization error: ' + err);
}
}
};
In the receivedEvent, which bubbles up, I call my initdb() function that calls the plugin and opens up the database.
The process works like a charm, in this method I can write my SQL SELECT statement to retrieve data and display it on the page without error.
As soon as I mode the TX script outside of this, it does not work. I even call the initdb() function before it, and still, I get an error saying that it cannot open database on undefined.
in core.js, at the top, I define db globally, as some have suggested in various other blogs, but the following code, out side of the receivedEvent just does not work:
jQuery(document).ready(function ($) {
app.initdb();
db.transaction(function (tx) {
tx.executeSql('SELECT * FROM table_1', [], function (tx, results) {
var _data = results;
for (var i = 0; i < results.rows.length; i++) {
var row = results.rows.item(i);
$li = $('<li></li>').text(row);
$('.table-output').append($li);
}
}, function (e) {
alert('an error occurred trying to retrieve database from table_1');
});
}, function (e) {
alert('an error occurd');
}, function () {
alert('all done');
});
});
after calling app.initdb() just before I handle a TX, my assumption is that it would open the database again, as at this point, right? Even if I don't use jQuery's ready statement, it just does not work, without jQuery:
app.initdb();
db.transaction(function (tx) {
tx.executeSql('SELECT * FROM table_1', [], function (tx, results) {
var _data = results;
for (var i = 0; i < results.rows.length; i++) {
var row = results.rows.item(i);
$li = jQuery('<li></li>').text(row);
jQuery('.table-output').append($li);
}
}, function (e) {
alert('an error occurred trying to retrieve database from table_1');
});
}, function (e) {
alert('an error occurd');
}, function () {
alert('all done');
});
I'm sure there is something that I'm not getting about this. Is it impossible to open the database and retrieve data outside of the device ready statement?

Resources