How to send Data from Server to client without persisting to DB

HI All,
I’m new to Meteor and been trying to implement some easy prototypes.
One of them involves calling a RESTful api that returns JSON objects. I was successful to have the server logging to the console all the results, but was never able to have the client access such data. I read all posts about sync and async calls and how to send that data.

Nothing worked… I tried HTTP.get() method with and without callback function and the same on the client Meteor.call() with and without call back function. The server always works.

Client never saw the data. I finally gave up and persisted the data on a Mongo collection and bingo… My client finally saw that. But, I was really hoping for a way to pass that data without meddling with Mongo, after all it’s meant to be transient data…

Here’s my latest try before I implemented mongo: (It even makes use of fibers/future library to help with sync calls.
Like I said before, I also tried without any function callback, but didn’t work either…

if (Meteor.isClient) {

  Template.body.helpers({
    getLatest: function () {
        console.log("Trying to get media from client");
//        Meteor.call("retrieveMedia", function(error, r) {
//            if (!error) {
//                console.log("returning " + r + " objects");
//                return r;
//            } else {
//               console.log(error);
//            }
        try {
            var r = Meteor.call("retrieveMedia");
            console.log(" retrieved " + r);
            return r;
        } catch (e) {
            console.log("caught exception " + e);
        }
        }

  });

}

if (Meteor.isServer) {

}


Meteor.methods({
    retrieveMedia: function () {
        console.log("retrieveMedial called!");
        if (Meteor.isServer) {
            var Future = Npm.require('fibers/future');
            var media = new Future();
            this.unblock();
            var r = HTTP.get("http://tools.cdc.gov/api/v2/resources/media?max=3",  function (error, result) {
                if(error) {
                    console.log('http get FAILED!');
                    media.throw(error);
                }
                else {
                    //console.log('http get SUCCES');
                    if (result.statusCode === 200) {
                        //console.log('Status code = 200!');
                        //console.log(result.data.results[0]);

                        media.return(result.data.results);
                    }
                }
            });
            media.wait();
         };
           // return r.data.results;
    }


});

Any help will be greatly appreciated as to how to fix this code.

thanks,
Marcelo.

2 Likes

I used Meteor.Methods on the server, and was able to transmit data (CSS files) back to the client. I ran into a problem the other day not getting “synchronous” data back:

    var data = Meteor.call('getsomething');

And in doing so switched to providing a callback, and it seemed to fix my probem:

        Meteor.call('getsomedata', 'dataname', function (err, result) {
            if (result) {
                processresult(result);
            } else if (err) {
                alert(err);
            }
        });

Have you tried with the callback format? Also, I noticed that you’re making a call to a government website from the server block of code. By it’s very nature I suspect that would be truly asynchronous, and you may need to use the callback notation as I did.

1 Like

thanks for the quick reply…
As you can see in my previous code, I had the calback function commented out. but yes… I tried that before.
Still no luck…

(as far as the government website, i do work for CDC :smile:)

I grab ephemeral data (this example is papertrail) and use a client only Mongo collection that I drop frequently, it’s basically just a scratchpad for holding data.

I create a client only collection
Under /client

Papertrail = new Mongo.Collection(null);

I create the server side method to query papertrail
Under /server

var ptToken = {
    "X-Papertrail-Token": "XXX"
}

/// an empty query returns the 100 most recent logs
var doPapertrail = function(query) {
    if (_.isUndefined(query)) {
        options = {
            headers: ptToken,
            timeout: 5000
        }
    } else {
        console.log(query);
        check(query, String);
        options = {
            headers: ptToken,
            timeout: 5000,
            params: {
                "q": query
            }
        }
    };
    var papertrailResponse = Meteor.http.call(
        "GET",
        "https://papertrailapp.com/api/v1/events/search.json", options
    );

    if (papertrailResponse.statusCode === 200) {
        return papertrailResponse;
    } else {
        console.log("ERROR");
    };
};

Meteor.methods({
    'searchPapertrail': function(query) {
        return doPapertrail(query);
    },
});

I call that method from the client side
under /client

searchPapertrail = function(q) {
    Session.set("isSearching", true);
	analytics.track("Searched Papertrail");
	Papertrail.remove({});
    Meteor.call('searchPapertrail', q, function(error, result) {
        q.length < 1 ? Session.set("statusMessage", "100 Recent Logs") : Session.set("statusMessage", "Logs matching " + q);
        Session.set("isSearching", false);
        Papertrail.insert(result.data);
    })
};

And the data in the client only collection (Papertrail) is returned to the user via a helper in the template

Template.papertrail.helpers({
    searchResult: function() {
        return Papertrail.findOne();
    },
    statusMessage: function() {
        return Session.get("statusMessage");
    },
    isSearching: function() {
        return Session.get("isSearching");
    }
});

Template.papertrail.events({
    'keypress [data-action="search-papertrail"]': function(event) {
        if (event.which == 13) {
            Session.set("statusMessage", null);
            var query = event.target.value
            if (query.length < 1) {
                Materialize.toast("Recent logs...", 2000);
            } else {
                Materialize.toast("Searching for " + query, 2000);
            }
            searchPapertrail(query);
        };
    },
    'click [data-action="clear-papertrail"]': function(event) {
        Papertrail.remove({});
        Session.set("statusMessage", null);
    }
});

Template.papertrail.onRendered(function() {
    Session.set("statusMessage", null);
});

Template.papertrail.onDestroyed(function() {
    Session.set("thisPage", null);
    Session.set("statusMessage", null);
});

Let me know if that helps - the client only collection is nice

2 Likes

@mscaldas: I took your code and reworked it as below. Note that this works as expected and the client page will be update reactively when the method returns:

In the .html file:

<head>
  <title>Government API Test</title>
</head>

<body>
  {{> media}}
</body>

<template name="media">
  {{#each getLatest}}
    <p>{{name}}: {{datePublished}}</p>
  {{/each}}
</template>

In the .js file:

if (Meteor.isClient) {
  Template.media.onCreated(function() {
    var self = this;
    self.resources = new ReactiveVar(null);
    Meteor.call("retrieveMedia", function(error, r) {
      if (!error) {
        self.resources.set(r);
      } else {
         console.log(error);
      }
    });
  });

  Template.media.helpers({
    getLatest: function () {
      var self = Template.instance();
      return self.resources.get();
    }
  });
}

if (Meteor.isServer) {
  Meteor.methods({
    retrieveMedia: function () {
      this.unblock();
      try {
        var result = HTTP.get("http://tools.cdc.gov/api/v2/resources/media?max=3");
      } catch (error) {
        throw new Meteor.Error(error.getMessage());
      }
      if (result.statusCode === 200) {
        return result.data.results;
      } else {
        throw new Meteor.Error('HTTP get status ' + result.statusCode);
      }
    }
  });
}

Finally, you should note that because of the way the method is invoked (in the template onCreated callback), it will only ever be called once. If you expect (or want) to track updates to the API endpoint this is not a good way to do it. In that case I would consider a non-persisted collection published from the server and updated on a timer - @ccorcos has published this on github, which looks like a pragmatic way of addressing the reactive consumption of REST endpoints.

2 Likes

I think this package + article maybe relevant
http://arunoda.github.io/meteor-streams/

1 Like

Thanks everyone… that was greatly appreciated. I tried both robfallows and ctaloi and they both worked. But because the API is slow, robfallows solutions seems to behave better.

But I learned about client-only collections (thanks CTaloi) and now I have to catch up on rob’s solution - I installed the extra package he was using on his solution (Reactive-var) and need to do some reading on that as well as Template resources.

Thanks all.

Maybe you are also interested in this thread: Meteor and the Internet of Things (IoT) - whats best to use? Collection, Meteor-Streams, Streamy

For future readers of this thread, I hunted around for how to do this for hours and there’s a lot of confusing and various threads… Here’s a really simple way to do exactly what the title of the post is referring to by creating a memory-only Collection that can send its data to the client (and back, see below)…

Setup Collection:

if(Meteor.isServer) {
   Widgets = new Meteor.Collection('widgets', {connection: null});
}

if(Meteor.isClient) {
   Widgets = new Meteor.Collection('widgets');
}

Publish:

Meteor.publish("allWidgets", function() {
  return Widgets.find();
});

Subscribe (on Client and/or in Route):

Meteor.subscribe('allWidgets');

In Template:

{{#each widgets}}
    <div>{{someAttribute}}</div>
{{/each}}

In Template Helper:

widgets: function() {
    return Widgets.find();
}

Call Meteor Method from the client to insert items into the Server memory only collection (still figuring out if there’s a way to call insert on the client - haven’t figured it out but this works:

Meteor.methods({
    'insertWidget': function(param1, param2) {
        Widgets.insert({param1: param1, param2: param2});
  }
})
10 Likes

Do we have a replacement for meteor streams ?

2 Likes