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

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.

Related

Returning values from a subscription callback

I am trying to get the value of a field from a document returned via a subscription. The subscription is placed inside a helper function. I had a callback function within the subscription return this value and then I assigned the return value to a variable (see code). Finally, I had the helper return this value. However, the value returned is a subscription object (?) and I can't seem to get anything out of it.
Code:
Template.myTemplate.helpers({
'returnUser':function(){
var id = Session.get('currentUserId');
var xyz = Meteor.subscribe('subscriptionName',id,function(){
var user = accounts.find().fetch({_id: id})[0].username;
return user;
}
return xyz;
}
});
Any help will be much appreciated. :)
You have to load first your subscriptions when the template is created, this creates an instance of your data with Minimongo.
Template.myTemplate.onCreated(function () {
var self = this;
self.autorun(function() {
self.subscribe('subscriptionName',id);
});
});
Then in the helper, you can make a query to retrieve your data
Template.myTemplate.helpers({
'returnUser': function(){
var id = Session.get('currentUserId');
return accounts.findOne(id).username;
}
});

Tracker.autorun and subscriptions from array

I registered three Tracker.autorun functions with collection from array: ['tags', 'allUsers', 'userGroups']
formObj.collections.forEach(collection => {
Tracker.autorun(() => {
const handle = Meteor.subscribe(collection);
if (handle.ready()) {
dispatch(collectionIsReady(formObj, collection));
console.log(collection);
if (_.isEqual(formObj.collections, formObj.loadedCollections)) {
dispatch(collectionsAreReady(formObj));
dispatch(formIsReady(formObj));
}
}
});
});
If one of these collections changed the Tracker.autorun function is executed but only with the collection name of the last array item (console output: "userGroups"). What should i do to get the correct collection name?
thanks for your help
Edit:
There is no need to know the correct collection name.
Because of correctly executed autorun functions i additionally passed the computation (c) to collectionIsReady().
formObj.collections.forEach(collection => {
Tracker.autorun((c) => {
const handle = Meteor.subscribe(collection);
if (handle.ready()) {
dispatch(collectionIsReady(formObj, collection, c));
if (_.isEqual(formObj.collections, formObj.loadedCollections)) {
dispatch(collectionsAreReady(formObj));
dispatch(formIsReady(formObj));
}
}
});
});

Meteor Reactive Session: Not Working (Why?)

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')}.

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?

Can't get result data with $http angularjs

I'm trying to use $http, but why it return null result?
angular.module('myApp')
.factory('sender', function($http) {
var newData = null;
$http.get('test.html')
.success(function(data) {
newData = data;
console.log(newData)
})
.error(function() {
newData = 'error';
});
console.log(newData)
return newData
})
Console say: http://screencast.com/t/vBGkl2sThBd4. Why my newData first is null and then is defined? How to do it correctly?
As YardenST said, $http is asynchronous so you need to make sure that all functions or display logic that are dependent on the data that is returned by your $http.get(), gets handle accordingly. One way to accomplish this is to make use of the "promise" that $http returns:
Plunkr Demo
var myApp = angular.module('myApp', []);
myApp.factory('AvengersService', function ($http) {
var AvengersService = {
getCast: function () {
// $http returns a 'promise'
return $http.get("avengers.json").then(function (response) {
return response.data;
});
}
};
return AvengersService;
});
myApp.controller('AvengersCtrl', function($scope, $http, $log, AvengersService) {
// Assign service to scope if you'd like to be able call it from your view also
$scope.avengers = AvengersService;
// Call the async method and then do stuff with what is returned inside the function
AvengersService.getCast().then(function (asyncCastData) {
$scope.avengers.cast = asyncCastData;
});
// We can also use $watch to keep an eye out for when $scope.avengers.cast gets populated
$scope.$watch('avengers.cast', function (cast) {
// When $scope.avengers.cast has data, then run these functions
if (angular.isDefined(cast)) {
$log.info("$scope.avengers.cast has data");
}
});
});
This JavaScript code is asynchronous.
console.log(newData)
return newData
Is executed before what inside success
newData = data;
console.log(newData)
So at first time, the newData is null (you set it to be null)
And when the http response is returned (inside the success), the newData gets its new value.
This is very common in Javascript, you should do all your work inside the success.

Resources