Publishing data from external API in Meteor - meteor

For publishing a list of items coming from an external API, I followed the this tutorial: so I've a publication on the server called "items" and a collection with the same name on the client; iron-router is the trait d'union between them. So far so good.
Now I'm stuck in implementing the "item detail" route (e.g. /item/:id). I wrote a server method like this:
Meteor.methods({
'getItem': function(id) {
check(id, Match.Integer);
var self = this;
var asyncCall = Meteor.wrapAsync(requestToThirdParty);
// requestToThirdParty is local function which calls HTTP.get
var response = asyncCall('GET', id);
return response.data;
}
});
I don't know if it's the best way to do, but I'm wondering how to call this method from the following route:
Router.route('/items/:id', {
name: 'itemDetail',
data: function() {
var item = Meteor.call('getItem', this.params.id); // this should be sync
console.log('item: '+item);
return item;
}
});
I'm sure that the method on server works fine (I debugged), but the console log in the client always shows "undefined".
What am I missing?
Are there any other approaches?

Related

Added field not published to client?

I'm doing an aggregation in Meteor where I'm trying to find 'thingies' within a given distance and publish it to the client:
Meteor.publish("thingieSearch", function(userId) {
check(userId, String);
var subscription = this;
var thingies = {};
var userId = this.userId;
var usrAcc = Meteor.users.findOne({_id: userId});
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var pipeline = [{
$geoNear: {
near: usrAcc.profile.location.geometry.coordinates,
distanceField: "calculatedDistance",
spherical: true,
limit: 100,
distanceMultiplier: 3959.2,
maxDistance: 0.075,
query: {
"status": "started",
"owner": {$ne: userId} },
}
}];
db.collection("thingies").aggregate(
pipeline,
Meteor.bindEnvironment(
function (err, result) {
console.log('result', result);
_.each(result, function (r) {
chases[r._id] = r;
subscription.added("thingieSearch", r._id, {
chase: r
});
})
}
)
);
subscription.ready();
});
When I do a console.log on the server side, it looks correct, the 'distanceField' that I specified as 'calculatedDistance' is calculated and shown as a field.
On the client side, I subscribe to this publication and I can see the thingies but I can't see the 'calculatedDistance' field.
Any idea why?
There are two things here.
1) Your publication will not reactively update data to the client, since you are using mongodb remote collection driver. If you intend it to be a non-reactive then you can use a meteor method instead of publication and call the method whenever userId changes.
2) I think you are using thingies collection on the client side and not seeing the calculatedDistance field. You need to create a client only collection (thingieSearch) to access the custom published results like this,
//On client side only
thingieSearch = new Mongo.Collection("thingieSearch");
thingieSearch.findOne(); // After the publication, you should be able to see the results with calculatedDistance
You should use whatever name you passed inside the subscription.added block to create collection. For example, if your publication has
subscription.added("thingieWithCalculateField", r._id, { chase: r });
you should do
//On client side only
thingieWithCalculateField = new Mongo.Collection("thingieWithCalculateField");
// instead of thingieSearch = new Mongo.Collection("thingieSearch");
See the counts-by-room publication in the Meteor.publish documentation for more details.

Implement added, changed and removed server side

Context : I am using a Collection Params to call method from the Server to a C app. The C app does its stuff and then calls the server by RPC to send me the results. With the result, I get the Params ID to delete the corresponding element.
With the deletion of the Element of Params, the C app gets a removed message. I want to prevent this behavior to avoid overloading the C app of messages.
I've thinked about implementing the removed event into the Publish method on the server to prevent the server from informing the C app. I just want the C app to be inform about added events.
On the Meteor Doc, there is an example of implementation of added and removed but I don't understand it. Can someone help me ?
I've tried this (don't work at all) :
Meteor.publish('expert_mode_parameters', function ()
{
var self = this;
var handle = Expert_Mode_Parameters.find().observeChanges({
added: function ()
{
return Expert_Mode_Parameters.find();
},
removed: function ()
{
return [];
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
}
It looks like your goal is to subscribe to a data set but only receive added messages, not changed or removed.
The below code should do this:
Meteor.publish('expert_mode_parameters', function () {
var self = this;
var handle = Expert_Mode_Parameters.find().observe({
added: function (document) {
self.added("expert_mode_parameters", document._id, document);
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
}
The concept is, you're watching the results of Expert_Mode_Parameters.find() and then calling self.added(document) when there is a new item. The same thing can easily be expanded to include changed.

Subscribe collection based on url parameters in meteor

I am trying to achieve a dynamic subscription of collection data based on the parameter in the url.
On Client side, I have the following subscription code in main.js file.
cSubscribe = Meteor.subscribe('cPublish', Session.get('param1'));
On Server, I have the following publish code in main.js file.
Test = new Meteor.Collection('test');
Meteor.publish('cPublish', function(param1) {
return Test.findOne({_id: param1});
});
In the router, I am setting the url parameter value in the Session, Session.set('param1', value); and when I try cSubscribe.ready(), it is returning false. Until the subscription is ready I am showing a loading template.
The route snippet,
'/test-url/:value': function(value) {
Session.set('param1', value);
if (cSubscribe.ready()) {
//some code
} else {
return 'loading';
}
}
What is wrong with the process ? Is there any better way of achieving dynamic subscription ?
First, I hope, you use iron-router (-:
In router, for example, in onBeforeAction() you have to set Session.set('param1', value);
Then on client try to use smth like this:
Meteor.startup(function() {
Deps.autorun(function() {
var param = Session.get("param1");
if(param) {
cSubscribe = Meteor.subscribe('cPublish', param);
});
});
In other words, you have to resubscribe after url changed.

How to emit data only to one client in Meteor streams

I am building a realtime game with Meteor streams. I need to update only one client - send a room ID from server. Users are not logged in so Meteor.userId() is null and therefore I can't use this: http://arunoda.github.io/meteor-streams/communication-patterns.html#streaming_private_page
There is only one URL (homepage) where all things happen. So I don't use any URL parameters for room. Everything is on the server.
I have tried to use Meteor.uuid() instead of Meteor.userId() but uuid is changed after each emit (which is strange).
In socket.io I would do this:
//clients is an array of connected socket ids
var clientIndex = clients.indexOf(socket.id);
io.sockets.socket(clients[clientIndex]).emit('message', 'hi client');
Is there any way to do this in Meteor streams or Meteor itself?
Well, this can be easily done if you decided to use database, but I guess it is not the best option if you have a large number of clients.
So another way to achieve this - without database - is to make a good use of the Meteor's publish/subscribe mechanism. Basically the way it could work is the following:
1. client asks server for a communication token (use Meteor.methods)
2. client subscribes to some (abstract) data set using that token
3. server publishes the required data based on the received token
So you will need to define a method - say getToken - on the server that generates tokens for new users (since you don't want to use accounts). This could be something more or less like this:
var clients = {}
Meteor.methods({
getToken: function () {
var token;
do {
token = Random.id();
} while (clients[token]);
clients[token] = {
dependency: new Deps.Dependency(),
messages: [],
};
return token;
},
});
A new client will need to ask for token and subscribe to the data stream:
Meteor.startup(function () {
Meteor.call('getToken', function (error, myToken) {
// possibly use local storage to save the token for further use
if (!error) {
Meteor.subscribe('messages', myToken);
}
});
});
On the server you will need to define a custom publish method:
Meteor.publish('messages', function (token) {
var self = this;
if (!clients[token]) {
throw new Meteor.Error(403, 'Access deniend.');
}
send(token, 'hello my new client');
var handle = Deps.autorun(function () {
clients[token].dependency.depend();
while (clients[token].messages.length) {
self.added('messages', Random.id(), {
message: clients[token].messages.shift()
});
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
and the send function could defined as follows:
var send = function (token, message) {
if (clients[token]) {
clients[token].messages.push(message);
clients[token].dependency.changed();
}
}
That's a method I would use. Please check if it works for you.
I think using Meteor.onConnection() like a login would enable you to do what you want pretty easily in a publish function.
Something like this:
Messages = new Meteor.Collection( 'messages' );
if ( Meteor.isServer ){
var Connections = new Meteor.Collection( 'connections' );
Meteor.onConnection( function( connection ){
var connectionMongoId = Connections.insert( connection );
//example Message
Message.insert( {connectionId: connection.id, msg: "Welcome"});
//remove users when they disconnect
connection.onClose = function(){
Connections.remove( connectionMongoId );
};
});
Meteor.publish( 'messages', function(){
var self = this;
var connectionId = self.connection.id;
return Messages.find( {connectionId: connectionId});
});
}
if ( Meteor.isClient ){
Meteor.subscribe('messages');
Template.myTemplate.messages = function(){
//show all user messages in template
return Messages.find();
};
}
I have used database backed collections here since they are the default but the database is not necessary. Making Messages a collection makes the reactive publishing easy whenever a new message is inserted.
One way that this is different from streams is that all the messages sent to all clients will end up being kept in server memory as it tries to keeps track of all data sent. If that is really undesirable then you could use a Meteor.method so send data instead and just use publish to notify a user a new message is available so call the method and get it.
Anyway this is how I would start.

Can I publish only Collections object in meteor?

On the introducing article of DDP, I read that anything can be published, but I've read somewhere ( for example in this Stackoverflow comment Publish arbitrary data and automatically update HTML ) that only Collections can be published.
So where is the truth? If we can publish other things than Collections, I would liek to see an example as I can't find one so far.
From the docs: http://docs.meteor.com/#meteor_publish
Publish functions can return a Collection.Cursor, in which case Meteor will publish that cursor's documents. You can also return an array of Collection.Cursors, in which case Meteor will publish all of the cursors.
So at the moment you can only return a Collection via a cursor (result of a Collection.find()).
To return other data you need to hack into the sockjs stream (the socket library meteor uses to communicate to the server). Be aware this does not guarantee compatibility with future versions of meteor. Sockjs is the library used for meteor to communicate between the server (the wire)
from Publish arbitrary data and automatically update HTML*
client side js
sc = new Meteor._Stream('/sockjs');
sc.on('message', function(payload) {
var msg = JSON.parse(payload);
Session.set('a_random_message', JSON.stringify(msg.data));
});
Template.hello.greeting = function () {
return Session.get('a_random_message');
};
server side js
ss = new Meteor._StreamServer();
ss.register(function (socket) {
var data = {socket: socket.id, connected: new Date()}
var msg = {msg: 'data', data: data};
// Send message to all sockets (which will be set in the Session a_random_message of the client
_.each(ss.all_sockets(), function(socket) {
socket.send(JSON.stringify(msg));
});
});
You can also look at Meteor Streams too. See below.
assume you have added meteor streams via atmosphere - mrt add streams
sc = new Meteor.Stream('hello');
if(Meteor.isServer) {
Meteor.setInterval(function() {
sc.emit('a_random_message', 'Random Message: ' + Random.id());
}, 2000);
Meteor.permissions.read(function() { return true });
}
if(Meteor.isClient) {
sc.on('a_random_message', function(message) {
Session.set('a_random_message', message);
});
Template.hello.greeting = function () {
return Session.get('a_random_message');
};
}

Resources