How do I reliably pull data from Meteor server collections to client collections when using an existing mongodb as MONGO_URL? - meteor

I know that there are several methods to share collections on both the client and server -- namely either in top level lib folder or publish/subscribe model -- but when I try either of these things when using mongodb running at localhost:27017 as my MONGO_URL, I am not reliably getting data on the client. Occasionally console.log(myCollection.findOne({})) will return expected data in the browser but most of the time it returns undefined.
//Client side code
Template.controls.onCreated(function controlsOnCreated() {
Meteor.subscribe("myEvents");
Events = new Mongo.Collection("events");
});
//Server side code
Meteor.startup(() => {
Events = new Mongo.Collection("events");
}
Meteor.publish('myEvents', function() {
console.log(Events.find());
return Events.find();
});
UPDATED CODE -- returns Events on server but not client:
//Client
Template.controls.onCreated(function controlsOnCreated() {
this.subscribe("myEvents");
});
//Server
if (Meteor.isServer) {
Meteor.publish("myEvents", function() {
return Events.find();
});
}
// /collections/events.js
Events = new Mongo.Collection("events");
UPDATE 2:
I am attempting to verify the publication in the browser after the page has rendered, calling Events.findOne({}) in the Chrome dev tools console.

on your client:
Template.controls.onCreated(function controlsOnCreated() {
Meteor.subscribe("myEvents");
Events = new Mongo.Collection("events");
});
that is an odd place to define the Events variable. typically, you would put that line of code in a JS file common to both platform. e.g.
collections/events.js:
Events = new Mongo.Collection("events");
when that line runs on the server, it defines the mongo collection and creates a server-side reference to it. when it runs on the client, it creates a collection by that name in mini-mongo and creates a client-side reference to it.
you can write your onCreated like this (note "this" instead of "Meteor"):
Template.controls.onCreated(function() {
this.subscribe("myEvents");
});
you don't say where on the client you ran your console.log with the find(). if you did it in the onCreated(), that's too early. you're seeing the effects of a race condition. typically, you might use it in a helper:
Template.controls.helpers({
events() {
return Events.find({});
}
});
and display the data in the view:
{{#each event in events}}
{{event.name}}
{{/each}}
that helper will run reactively once the data from the publish shows up.

Related

Insert new collection after function runs on server

When I return the geocode from googles API I'm trying to save it into my database. I've been trying to use the code below, to just insert a Test document with no luck. I think it has something to do with meteor being asynchronous. If I run the insert function before the googleMapsClient.geocode function it works fine. Can someone show me what I'm doing wrong.
Meteor.methods({
'myTestFunction'() {
googleMapsClient.geocode({
address: 'test address'
}, function(err, response) {
if (!err) {
Test.insert({test: 'test name'});
}
});
}
});
I see now where you got the idea to run the NPM library on the client side, but this is not what you really want here. You should be getting some errors on the server side of your meteor instance when you run the initial piece of code you gave us here. The problem is that the google npm library runs in it's own thread, this prevents us from using Meteor's methods. The easiest thing you could do is wrap the function with Meteor.wrapAsync so it would look something like this.
try {
var wrappedGeocode = Meteor.wrapAsync(googleMapsClient.geocode);
var results = wrappedGeocode({ address : "testAddress" });
console.log("results ", results);
Test.insert({ test : results });
} catch (err) {
throw new Meteor.Error('error code', 'error message');
}
You can find more info by looking at this thread, there are others dealing with the same issue as well
You should run the googleMapsClient.geocode() function on the client side, and the Test.insert() function on the server side (via a method). Try this:
Server side
Meteor.methods({
'insertIntoTest'(json) {
Test.insert({results: json.results});
}
});
Client side
googleMapsClient.geocode({
address: 'test address'
}, function(err, response) {
if (!err) {
Meteor.call('insertIntoTest', response.json);
}
});
Meteor Methods should be available on the both the server and client sides. Therefore make sure that your method is accessible by server; via proper importing on /server/main.js or proper folder structuring.
(If a method contains a secret logic run on the server, it should be isolated from the method runs on both server & client, though)

Meteor collection updated on server, not reflecting on client

I am trying to create a Meteor app that stores content in a Meteor collection to be passed between the server and the client to display a success message after an asynchronous api call through the twit package.
However, I am running into an issue where when I update the collection on the server and the updates are not reflected on the client. My code is as follows:
/lib
Alerts = new Meteor.Collection("alerts");
/client
Template.suggestionForm.events({
"submit form": function (e) {
return Meteor.call('submitMessage', message);
}
});
Meteor.subscribe('alerts');
Meteor.startup(function() {
Tracker.autorun(function() {
console.log(Alerts.find());
})
});
/server
Fiber = Npm.require('fibers')
Twit = new TwitMaker({
consumer_key: '...',
consumer_secret: '...',
access_token: '...',
access_token_secret: '...'
});
Meteor.publish("alerts", function(){
Alerts.find();
});
Meteor.methods({
submitMessage: function(message) {
this.unblock();
Twit.post('statuses/update', { 'status': message }, function(err, data, response) {
Fiber(
Alerts.remove({});
Alerts.insert({response: err});
).run();
}));
}
});
When I submit the form the function calls just fine and updates the collection, however the Tracker.autorun() does not run. Any ideas why this is happening or how I can make the client listen for changes in collections would be super helpful. Thank you!
Remember to return the resulting cursor in the publish():
Meteor.publish("alerts", function(){
return Alerts.find();
});
Reference: http://docs.meteor.com/#/full/meteor_publish
Publish functions can return a Collection.Cursor, in which case Meteor will publish that cursor's documents to each subscribed client. You can also return an array of Collection.Cursors, in which case Meteor will publish all of the cursors.
and
Alternatively, a publish function can directly control its published record set by calling the functions added (to add a new document to the published record set), changed (to change or clear some fields on a document already in the published record set), and removed (to remove documents from the published record set). These methods are provided by this in your publish function.
If a publish function does not return a cursor or array of cursors, it is assumed to be using the low-level added/changed/removed interface, and it must also call ready once the initial record set is complete.

Publishing Meteor Users to Admin

I'm trying to publish all emails and "roles" to "admin" users (using the meteor-roles meteorite package), and the server knows what it's trying to publish, but for some reason the client is not picking up the data. Here's the piece of code involved:
Server Code:
Meteor.publish("directory", function() {
if(Roles.userIsInRole(this.userId, 'admin')) {
//next line shows that it outputs that there are 2 documents in this
console.log(Meteor.users.find({}, {fields:{emails:1, roles:1}}).count());
return Meteor.users.find({}, {fields:{emails:1, roles:1}});
} else {
return {};
}
}
Client Code:
Meteor.subscribe("directory");
Directory = new Meteor.Collection("directory");
//and then to simulate my use for this collection
console.log(Directory.find().count());
//which outputs 0
Why isn't the client getting the documents? (and I am definitely logging in with an account with role "admin")
Thanks!
EDIT and SOLUTION!
Okay, so I figured out what I was doing wrong. I just need to publish "directory" on the server, and subscribe on the client. Then, all user data goes into the Meteor.users collection, and I shouldn't define my own "Directory=new Meteor.Collection('directory')". I can then access the data via Meteor.users. Cheers!
Server: use same code as above
Client:
Meteor.subscribe("directory");
console.log(Meteor.users.find().count()); //outputs 2, yay!
Your collection should probably be defined on the beginning so that client knows where to write!
Also, your Meteor.subscribe("directory"); runs once when the app is loaded. You should make it reactive.
Directory = new Meteor.Collection("directory");
Deps.autorun(function(){
Meteor.subscribe("directory");
});

Signalr (1.0.0-alpha2) Hubs - Can you add client functions after connection has been started?

Using Signalr (1.0.0-alpha2), I want to know if it is possible to add client functions after a connection has been started.
Say I create my connection and grab the proxy. Then I add some Server Fired client functions to the hub to do a few things. Then I start my connection. I then want to add some more Server Fired functions to my hub object. Is this possible?
var myHub= $.connection.myHub;
myHub.SomeClientFunction = function() {
alert("serverside called 'Clients.SomeClientFunction()'");
};
$.connection.hub.start()
.done(function() {
myHub.SomeNewClientFunction = function() {
alert("serverside called 'Clients.SomeNewClientFunction()'");
}
})
This example is not realistic, but I basically want to send my 'myHub' variable to a different object after the hub is started to subscribe to new events that the original code did not care for.
Real Life Example: A dashboard with a number of different hub events (new site visits, chat message, site error). I 'subscribe' after the connection has started and then pass my hub proxy to all of my different UI components to handle their specific 'message types'. Should I create separate Hubs for these or should I be able to add more Server Fired client functions on the fly?
Yes you can. Use the .on method.
Example:
myHub.on('somethingNew', function() {
alert("This was called after the connection started!");
});
If you want to remove it later on use the .off method.
I have the exact same situation. You might want to consider adding another layout of abstraction if you're trying to call it from multiple places.
Here's a preliminary version of what I've come up with (typescript).
I'll start with the usage. SignalRManager is my 'manager' class that abstracts my debuggingHub hub. I have a client method fooChanged that is triggered on the server.
Somewhere in the module that is using SignalR I just call the start method, which is not re-started if already started.
// ensure signalR is started
SignalRManager.start().done(() =>
{
$.connection.debuggingHub.server.init();
});
Your 'module' simply registers its callback through the manager class and whenever the SignalR client method is triggered your handler is called.
// handler for foo changed
SignalRManager.onFooChanged((guid: string) =>
{
if (this.currentSession().guid == guid)
{
alert('changed');
}
});
This is a simple version of SignalRManager that uses jQuery $.Callbacks to pass on the request to as many modules as you have. Of course you could use any mechanism you wanted, but this seems to be the simplest.
module RR
{
export class SignalRManager
{
// the original promise returned when calling hub.Start
static _start: JQueryPromise<any>;
private static _fooChangedCallback = $.Callbacks();
// add callback for 'fooChanged' callback
static onfooChanged(callback: (guid: string) => any)
{
SignalRManager._fooChangedCallback.add(callback);
}
static start(): JQueryPromise<any>
{
if (!SignalRManager._start)
{
// callback for fooChanged
$.connection.debuggingHub.client.fooChanged = (guid: string) =>
{
console.log('foo Changed ' + guid);
SignalRManager._fooChangedCallback.fire.apply(arguments);
};
// start hub and save the promise returned
SignalRManager._start = $.connection.hub.start().done(() =>
{
console.log('Signal R initialized');
});
}
return SignalRManager._start;
}
}
}
Note: there may be extra work involved to handle disconnections or connections lost.

Meteor - Using collection on client startup

Why this code shows "0"? Shouldn't it return "1"?
Messages = new Meteor.Collection("messages");
if (Meteor.is_client) {
Meteor.startup(function () {
alert(Messages.find().count());
});
}
if (Meteor.is_server) {
Meteor.startup(function () {
Messages.insert({text: "server says hello"});
});
}
If I do the "Messages.find().count()" later, it returns 1.
By default, when a Meteor client starts up, it connects to the server and subscribes to documents in any Meteor.Collection you defined. That takes some time to complete, since there's always some amount of delay in establishing the server connection and receiving documents.
Meteor.startup() on the client is a lot like $() in jQuery -- it runs its argument once the client DOM is ready. It does not wait for your client's collections to receive all their documents from the server. So the way you wrote the code, the call to find() will always run too early and return 0.
If you want to wait to run code until after a collection is first downloaded from the server, you need to use Meteor.subscribe() to explicitly subscribe to a collection. subscribe() takes a callback that will run when the initial set of documents are on the client.
See:
meteor-publish
and meteor-subscribe
Just to follow up with a code example of how to know when a collection is ready to use on the client.
As #debergalis described, you should use the Meteor.subscribe approach - it accepts a couple of callbacks, notably onReady
For example:
if(Meteor.isClient){
Meteor.subscribe("myCollection", {
onReady: function(){
// do stuff with my collection
}
});
}

Resources