Waiting for meteor collection to finish before next step - javascript

I have a Meteor template that should be displaying some data.
Template.svg_template.rendered = function () {
dataset_collection = Pushups.find({},{fields: { date:1, data:1 }}, {sort: {date: -1}}).fetch();
a = moment(dataset_collection[0].date, "YYYY/M/D");
//more code follows that is also dependent on the collection being completely loaded
};
Sometimes it works, sometimes I get this error:
Exception from Deps afterFlush function: TypeError: Cannot read property 'date' of undefined
I'm not using Deps in any context. As I understand it, the collection is being referenced before it is completely finished loading.
I therefore would like to figure out how to simply say "wait until the collection is found before moving on." Should be straightforward, but can't find an updated solution.

You are right, you should ensure that code depending on fetching the content of a client-side subscribed collection is executed AFTER the data is properly loaded.
You can achieve this using a new pattern introduced in Meteor 1.0.4 : https://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe
client/views/svg/svg.js
Template.outer.onCreated(function(){
// subscribe to the publication responsible for sending the Pushups
// documents down to the client
this.subscribe("pushupsPub");
});
client/views/svg/svg.html
<template name="outer">
{{#if Template.subscriptionsReady}}
{{> svgTemplate}}
{{else}}
Loading...
{{/if}}
</template>
In the Spacebars template declaration, we use an encapsulating outer template to handle the template level subscription pattern.
We subscribe to the publication in the onCreated lifecycle event, and we use the special reactive helper Template.subscriptionsReady to only render the svgTemplate once the subscription is ready (data is available in the browser).
At this point, we can safely query the Pushups collection in the svgTemplate onRendered lifecycle event because we made sure data made its way to the client :
Template.svgTemplate.onRendered(function(){
console.log(Pushups.find().fetch());
});
Alternatively, you could use the iron:router (https://github.com/iron-meteor/iron-router), which provides another design pattern to achieve this common Meteor related issue, moving subscription handling at the route level instead of template level.
Add the package to your project :
meteor add iron:router
lib/router.js
Router.route("/svg", {
name: "svg",
template: "svgTemplate",
waitOn: function(){
// waitOn makes sure that this publication is ready before rendering your template
return Meteor.subscribe("publication");
},
data: function(){
// this will be used as the current data context in your template
return Pushups.find(/*...*/);
}
});
Using this simple piece of code you'll get what you want plus a lot of added functionalities.
You can have a look at the Iron Router guide which explains in great details these features.
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md
EDIT 18/3/2015 : reworked the answer because it contained outdated material and still received upvotes nonetheless.

This is one of those problems that I really wish the basic meteor documentation addressed directly. It's confusing because:
You did the correct thing according to the API.
You get errors for Deps which doesn't point you to the root issue.
So as you have already figured out, your data isn't ready when the template gets rendered. What's the easiest solution? Assume that the data may not be ready. The examples do a lot of this. From leaderboard.js:
Template.leaderboard.selected_name = function () {
var player = Players.findOne(Session.get("selected_player"));
return player && player.name;
};
Only if player is actually found, will player.name be accessed. In coffeescript you can use soaks to accomplish the same thing.
saimeunt's suggestion of iron-router's waitOn is good for this particular use case, but be aware you are very likely to run into situations in your app where the data just doesn't exist in the database, or the property you want doesn't exist on the fetched object.
The unfortunate reality is that a bit of defensive programming is necessary in many of these cases.

Using iron-router to wait on the subscription works, but I like to keep subscriptions centrally managed in something like a collections.js file. Instead, I take advantage of Meteor's file load order to have subscriptions loaded before everything else.
Here's what my collections.js file might look like:
// ****************************** Collections **********************************
Groups = new Mongo.Collection("groups");
// ****************************** Methods **************************************
myGroups = function (userId) {
return Groups.find({"members":{$elemMatch:{"user_id":userId}}});
};
// ****************************** Subscriptions ********************************
if(Meteor.isClient){
Meteor.subscribe("groups");
}
// ****************************** Publications *********************************
if(Meteor.isServer){
Meteor.publish("groups", function () {
return myGroups(this.userId);
});
}
I then put collections.js into a lib/ folder so that it will get loaded prior to my typical client code. That way the subscription is centralized to a single collections.js file, and not as part of my routes. This example also centralizes my queries, so client code can use the same method to pull data:
var groups = myGroups(Meteor.userId());

Related

Pass event object into template using Meteor

I am building an application for customizing the product grid order of an e-commerce website. Being able to present product order in responsive and flexible ways, would really make it easier for project managers to create a better experience for their customers.
Part of this grid order application is based on an article by Ryan Glover (to whom I owe many thanks), called Importing CSV's https://themeteorchef.com/tutorials/importing-csvs, using PapaParse.
Even though this the templates and build is in Meteor, this is a bit of a vanilla JavaScript question.
Here's my pretty stripped down question: How do I get the event object into the template? I get the event object fine from the Papa.parse() function, and could console.log(it). But, how do I pass the event object into the template.
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './main.html';
// NOTE: trying to figure out how to pass event object to template
Template.upload.events({
'change [name="uploadCSV"]' ( event, template ) {
// Handles the conversion and upload
var fileInput = document.querySelector('[name="uploadCSV"]');
// Parse local CSV file
Papa.parse(fileInput.files[0], {
header: true,
complete: function(results) {
// console.log(results); // includes data, error, and misc
// NOTE: This is the data object to send to the template
let itemData = results.data;
console.log(itemData) // here is the data object
// This test correctly iterates over the object, but should be done in the template
itemData.forEach(function(item) {
console.log(item)
// console.log(item['itemcode'])
});
// HELP! How do I send the object itemData to the template?
} // END complete
}); // END parse
} // END change
}) // END events
Here is a link to the repo. https://github.com/dylannirvana/gridorderapp/tree/master/meteor/vanilla
This has got to be embarrassingly easy. Many thanks in advance!
looking on your project will make clear that this piece will not be ready to go. Your question is not somethin about events but understanding meteor. The are essentials missing.
First check your grid template, there is no data defined. Second, understand Papa.parse on client and on server via Meteor.method
Not sure how you concept should run, but lets assume, that someone should be able to upload an CSV file with data to show in page.
Is this something which is persistant data? Means, should the data be stored or will it be uploaded all the time?
In case of persistant, you should use Meteor.method and a publish and subscribe collection from server to client. You may have a look at the meteor blaze todo app tutorial.
In case of temporary data, you may create a client only minimongo collection and subscribe the client to that.
While getting data from Parser you have to insert or add that content to your local or global collection.
When doing this and having a reactive (subscribed) template to the collection it will automtically show its content.
Again, you should get familar with the meteor blaze todo tutorial, to understand your needs.
Hope that will guide you the right way.
Good luck
Tom

Using the .find().fetch() from within a function in Meteor

I am making a project with Meteor and I'm having some issues trying to get data out of mongodb in JavaScript. I have the following in a function:
console.log(Time.find({today: "Saturday"}).fetch());
In my publish.js file on the server side I have the following:
Meteor.publish("time", function () {
var currentUserId = this.userId;
return Time.find({user: currentUserId});
});
And In my subscriptions file I have the following:
Meteor.subscribe("time");
This function gets called later down in the code but it returns an empty array. If I run this code in my browsers console it returns an array with 2 objects in it, which is correct. This leads me wondering if I can use the .fetch() function from within my code? As if I leave off the .fetch() it returns what looks like the usual giant object. My real problem is I need the data in the form that .fetch() gives it to me in. I think it's because the function gets triggered before the data gets a chance to load in, as if I switch out the .fetch() for a .count() it returns 0.
Is there any way around this or a fix?
Where are you you running that console.log?
There are a couple fundementals here that I believe you may have glossed over.
1 Pub / Sub
This is how we get data from the server, when we subscribe to a publication i becomes active and begins to send data, this is neither instant or synchronous, (think of it more like turning on a hose pipe), so when you run your console.log, you may not yet have the data on the client.
2 Reactive contexts
One of the fundamental aspects to building anything in meteor is its reactivity. and it helps to start thinking in terms of reactive and non reactive contexts. A reactive context is one that re-runs each time the data it depends on changes. Using an autorun (Tracker.autorun or this.autorun insdie a template lifecycle callback) or a template helper are good examples. By placing it in a template helper it will re-run when the data is available.
Template.Whatever.helpers({
items: function() {
// ...do your find here.....
}
});
As items is a reactive context, depending on the collection data, it re-run when that changes, giving you access to the data when the client has them.
3 Retrieving Non Reactive Data
Alternatively it is also possible to retrieve data non-reactively by using Meteor.call with a meteor method, and then doing something with the result, in the callback to the Meteor.call. Depending on what you're doing, Meteor.wrapAsync may also be your friend here.
a simple example (out of my head, untested) :
// on the server
Meteor.methods({
gimmeStuff: function() {
return "here is your stuff kind sir!";
}
});
// on the client
Meteor.call('gimmeStuff', function(err, result) {
if (err || !result) {
console.log("there was an error or no result!");
return false;
}
console.log(result);
return result;
});
4 Its Unlikely that you actually need ithe .fetch()
If you're working with this in a template, you don't need a fetch.
If you want this to be non-reactive you don't need a fetch
As one of the commenters mentioned, a cursor is just a wrapper around that array, giving you convenient methods, and reactivity.
5 Go Back to the Begining
If you haven't already, I would highly recommend working through the tutorial on the meteor site carefully and thoroughly, as it covers all of the essentials you'll need to solve far more challenging problems than this, as well as, by way of example, teach you all of the fundamental mechanics to build great apps with Meteor.

Loading and waiting for "global" meteor subscriptions

Here's my scenario:
I have a layout template that needs to check to see if a User belongs to at least one Team. If not, then display a div across the entire site. A user can only see the teams they belong to, so I created a simple publication that works: (code samples are CoffeeScript)
Meteor.publish 'teams', ->
return null if !#userId
Teams.find {'members._id': #userId}
This works great and Teams.find().fetch() in console gives expected results.
However, if I put this code in, say, the Template.layout.rendered, it doesn't work.
Template.layout.rendered = ->
teams = Teams.find().fetch()
hasTeams = teams.length > 0
if !hasTeams
...do stuff..
Obviously this doesn't work because the Teams find is async and not loaded when it needs to make the decision. With a normal template / page I would just use the IronRouter waitOn() but what do I do on the layout?
I could do a waitOn in my router, but since the data is "global" and going to be used everywhere, and because a user could deep link into the site all over the place, I don't want to add that waitOn to EVERY single route.
So what is the proper pattern? How do I get the meteor client to load global data and wait for the data before running the route?
More thinking and searching found the answer right here on SO: struggling to wait on subscriptions and multiple subscriptions
I changed my Router.configure to this:
Router.configure
layoutTemplate: 'layout'
waitOn: ->
return [
Meteor.subscribe('teams')
]
Multiple subscriptions can be added to the return array, and I believe it will wait on all of them.
I had a similar issue with iron router subscribing to a chat in two different publications with waitOn.
Meteor.subscribe('chats')
Only when I switched positions of the following publish blocks, would it let me see any data on the route. The following is the correct order for the two.
Meteor.publish("chats", function () {
return Chats.find();
});
Meteor.publish("chats", function(id) {
return Chats.find({eventRoom: id});
});

meteor client async pattern / how to implement a waitOn for a list of subscriptions w callbacks

for various reasons I'm writing an app that doesn't use IronRouter, but has to implement some similar logic. One is waiting for a list of subscriptions to be ready.
Since this is an async call client side in meteor, what are some techniques for doing this?
If I want to have a list of subs like:
sublist = [
PubSubMan.subscribe("Players"),
PubSubMan.subscribe("Stuff")
]
then start with the rest of the app once they are all .ready() = true
what is a good way to do this?
I can't quite understand how the wait() method is implemented in the IR source code here
This seems an ideal case for an aysnc.js type situation, where I want to call a list of methods and continue when their callbacks are done, but resorting to node style patterns seems a bit clunky for meteor. I looked at wrapAsync and meteorhacks async utils but that seems mostly for server methods and wrapping NPM packages.
If I could sum the list of ready() values and then create a Tracker deps that would fire if/when that sum changed... ? but not quite sure how to do that either.
Since each of the subscriptions fires a callback when done, i guess i could use a counter to track when callbacks are fired and keep a counter to check == length of the array, but again that seems kind of inelegant.
EDIT:
This isn't an ideal solution but the below works. But I still think I'm missing a more elegant method.
subList = [
PubSubMan.subscribe("Players"),
PubSubMan.subscribe("Stuff" )
]
Tracker.autorun (c) =>
subReady = _.filter subList, (item) ->
return item.ready()
allDone = (subList.length == subReady.length)
console.log("subs status: #{subReady.length} / #{subList.length} = ready: #{allDone}")
if allDone
c.stop()
startMainLoop()
related to this question on how tracker.autorun picks its computation dependencies
how does Tracker.autorun pick out its computation?
I'm not sure what your startMainLoop is like, but here's an approach that might work for you. Create a new template which just checks if all the subscriptions are ready; if they are it renders the real main template, and if they aren't then it renders a loading template.
<template name="subscriptionsReadyCheck">
{{#if allSubsAreReady}}
{{> mainTemplate}}
{{else}}
{{> loadingTemplate}}
{{/if}}
</template>
subList = [...]
Template.subscriptionsReadyCheck.helpers {
allSubsAreReady: -> _.every(subList, (sub) -> sub.ready())
}
This assumes that the subscriptions are created when the page loads and stays around until the page is closed. If you need subscriptions which are only created when the template is rendered and are stopped when the template is destroyed, you can store them on the template instance:
Template.subscriptionsReadyCheck.created = ->
#subList = [...]
Template.subscriptionReadyCheck.destroyed ->
for sub in #subList
sub.stop()
Template.subscriptionsReadyCheck.helpers {
allSubsAreReady: -> _.every(Template.instance().subList, (sub) -> sub.ready())
}

Creating AngularJS object that calls method on app load

I am wondering if there is a convention within AngularJS for creating an object that lives within the app module, but is not attached directly to the view in any way, but is called when the view has loaded and the app starts up. In particular, I am trying to write an object that dispatches messages to listening controllers when they come in from the server.
Currently, I have implemented this by creating a "Controller" that attaches to the view. It has a monitor() function that is called when the page loads, and then listens in a loop for any incoming messages. I call the monitor() function from within the loaded view, by setting the ng-controller like so:
<div ng-controller="MyController">
{{ monitor() }}
</div>
This doesn't feel like the right thing to do. This "Controller" isn't interacting with the view in any way, so my gut tells me I am violating principles of AngularJS. But I haven't been able to turn up an easy solution that is endorsed by the AngularJS doc.
I am looking for a way to create an object that lives within the AngularJS world (in other words, it can use dependency injection to get access to services, and it can use $scope.$broadcast to send messages to other listening controllers), but that doesn't need to attach itself to the view in any way.
Ideally, I am looking for a way to say, "Here Angular, on startup, create this object, and run this method on it." Is there a way to do this?
You may use this as a starting point:
declaration of your object.
AngularJS: Service vs provider vs factory
myApp.factory('MessageBus', function() {
return {
listeners: [],
init: function() {
// do whatever you need at startup
},
pushMessage: function(msg) {
angular.forEach(this.listeners, function(listener) {
listener(msg);
});
},
subscribe: function(onMessageCallback) {
this.listeners.push(onMessageCallback);
}
};
});
calling a method on angular appilcation start
https://docs.angularjs.org/api/ng/type/angular.Module#run
myApp.run(function(MessageBus) {
MessageBus.init();
});
using this object within controllers
https://docs.angularjs.org/guide/di
myApp.controller('MessageCtrl', function($scope, MessageBus) {
$scope.messagesToShow = [];
MessageBus.subscribe(function(message) {
$scope.messagesToShow.push(message);
});
$scope.submitMessage = function(id, text) {
MessageBus.pushMessage({
type: 'TEXTMESSAGE',
id: id,
payload: text
});
};
});
Note that this is something to start with and nothing for any production code. For example the controller doesn't unsubscribe after being destroyed - if the page changes - and so you leak memory.
Don't use $broadcast-events for this
1: they are slow
2: if this MessageBus has a specific concern, than in should be an own object with a meaningfull name and api. Otherwise your $rootScope will be flooded with thousends of different events for different concerns when your application grows. A service is always easier to document and you have a clean dependency on that specific service. Only using events on the $rootScope hides this dependency from every developer reading and hopefully understanding your codebase,
Yeah you approach is really smelly. This function will be called every time a $apply/$digest invokes.
Maybe move the function into the run callback on the module.
var app = angular.module("YourApp", [//dependencies]);
app.run(function($YourUIService){
$YourUIService.monitor();
});
The run will be invoked, when your angularjs-module has loaded every dependency and is ready to run.
Didn't find the doc for this :/

Categories

Resources