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');
};
}
Related
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.
Is it possible to preserve pointers (object references) when you send data from the server to the client with Meteor? My initial tests say No, but perhaps there is another way to do this.
Here are server-side and client-side scripts:
Server.js
Meteor.methods({
test: function test() {
var object = {
key: ['value']
}
var output = {
object: object
, reference: object.key
}
console.log(output.reference[0])
output.object.key[0] = "changed"
console.log(output.reference[0])
return output
}
})
Server output:
// value
// changed
Client.js
Meteor.call("test", testCallback)
function testCallback(error, data) {
if (!error) {
console.log(data.reference)
data.object.key[0]= "edited"
console.log(data.reference)
}
}
Client output in the console:
// changed
// changed
No, this is not possible, although subscriptions do something similar. If your data is not in a collection, you can still publish it (see this.added and friends in the Meteor docs), and the data will show up in a collection in the client.
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?
I am very new to Meteor so if my apologies if this is a dumb question:
How do you read a file from the filesystem on the server side, and get those contents displayed on the client side. Doing it synchonously isn't a problem because I can return it right away but as I understand from reading that defeats the purpose of Meteor so I am trying to do it asynchronously. However I dont know how to connect the client/server in this fashion. Publish/Subscribe seem to only be for db collections and not filesystem files. Any guidance you can give me would be very much appreciated.
if (Meteor.isClient) {
Template.body.helpers({
myfile: function(){
return file_contents;
}
});
}
if (Meteor.isServer) {
Meteor.methods({
myfile: function(){
var fs = Npm.require("fs");
fs.readFile('/opt/SI/SIAgent/conf/myfile','utf-8', function (err, data) {
file_contents = data;
});
}
});
}
You have to use Meteor.wrapAsync to turn Node.JS fs.readFile into synchronous-looking code so that you can return the result within the Meteor method.
var fs=Npm.require("fs");
fsReadFileSync=Meteor.wrapAsync(fs.readFile,fs);
Meteor.methods({
getMyFileContent:function(){
return fsReadFileSync("/opt/SI/SIAgent/conf/myfile","utf-8");
}
});
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.