Meteor.deps.Context and Invalid Collection of Documents - meteor

What's the difference with the following two blocks of code? The top works as expect, but the bottom does not.
// Initially outputs 0, but eventually outputs the # of players.
Meteor.autorun(function() {
var players = Players.find();
console.info(players.count());
});
// Outputs 0 twice. Why does this not work like the block above?
var players = Players.find();
Meteor.autorun(function() {
console.info(players.count());
});
I'm testing this in the leaderboard example, within the Meteor.isClient block.
Thank you,
Andrew

While Meteor is reactive you need to make your query within a reactive context a.k.a the Meteor.autorun. The reactive contexts are: Template, Meteor.autorun, Meteor.render and Meteor.renderList.
In the second case var players = Players.find(); is run while Meteor is starting up, and contains the data it got while querying at that time, while starting up.
In the first you've placed the query in a reactive context. Which is recalled and rerun whenever there is a data update of a sort. In the second case it doesn't get a chance to rerun the query it it remains with the data contained while the browser just loaded the page up.
While Meteor is reactive you still need to re query the data within the reactive context.

Related

When do Template.currentData() and template.data differ in value?

I know one is a reactive source, while the other is not. But I thought they would always give the same value.
Then I found the following code in Telescope's source:
var newTerms = Template.currentData().terms; // ⚡ reactive ⚡
if (!_.isEqual(newTerms, instance.data.terms)) {
instance.postsLimit.set(instance.data.terms.limit || Settings.get('postsPerPage', 10));
}
link: https://github.com/TelescopeJS/Telescope/blob/master/packages/telescope-posts/lib/client/templates/posts_list/posts_list_controller.js#L33
So it seems these two values can sometimes differ. When?
According to Meteor's documentation, about template.data:
This property provides access to the data context at the top level of
the template. It is updated each time the template is re-rendered.
Access is read-only and non-reactive.
Since we know that the current data context is reactive, hence can change without the template being re-rendered (which is what makes reactivity look all nice and smooth on Blaze), this if statement is written to check if the "real" current terms (which are stored in the reactive Template.currentData()) have changed compared to the "previous" terms we had the last time the current template was rendered. (stored in the non-reactive template.data)
To wrap it up, what this autorun does is:
Anytime the current data context changes...
Fetch the terms from said data context
Compare these terms to the ones stored in template.data when the template was rendered
If they differ, that means the terms have changed (duh): reset the post limit.
Within post_list_controller.js in the Template.onCreated() there is a section with:
// initialize the reactive variables
instance.terms = new ReactiveVar(instance.data.terms);
This reactive variable obtains it's terms value through:
{{> Template.dynamic template=template data=data}} // see posts_list_controller.html
Whenever you pass that template twice with a different set of data (David Weldon - scoped-reactivity), that reactive variable will be set and will cause the autorun to run.
// this part will cause the autorun to run
var terms = Template.currentData().terms; // ⚡ reactive ⚡

Making dc.js charts reactive in Meteor

I'm using dc.js and crossfilter to create some charts in Meteor. Everything is set, I have all the charts ready, but there is 1 question regarding making the charts reactive. I subscribe number of collections from mongoDB on client side in JavaScript files; here, I have to update the data that I pass to crossfilter every time the data in collections change.
Suppose, for each page on the app I'm subscribing to 8 different collections, each of the collections have about 30 data fields/columns. This data in every field keeps changing or updating for every 30 seconds or so. Now the question is, as the data changes charts in the page must be updated with new data and re-render all the charts.
I learned that there are a couple of methods in Meteor which makes the page reactive like deps.autorun( ) and tracker.autorun( ). The problem is, what code should I put in the above functions to make my charts reactive?
PS: please comment back if you need any clarification, the question is already getting lengthy.
Say you have a 'dcChart' template and you are calling that template from your main page as
{{> dcChart data=dcChartData}}
helper for data as:
'dcChartData': function () {
var data = SomeCollection.find(...);
return data;
}
Your code should look something like this:
Template.dcChart.rendered = function () {
var template = this;
buildChart(template.data);
template.autorun(function () {
var templateData = Template.currentData();
buildChart(templateData);
});
}
In the autorun section you rerender the chart by passing the reactive data(records from the collection). So every time the collection is updated the chart will be rendered again).

Limiting Children of Object in Firebase

I am looking to get back my whole object, but limit one of my children objects.
For example, say you take a chat app like firebase does and you do "rooms".
So you might have
rooms: {
mainroom:{
name: something,
otherAttrs: mfasfd,
messages: {
0: {
message: something
},
1: {
message: something else
}
}
}
I may have 300 messages in that mainroom, but I want to limit it to 30 say. This example is basic, but in my actual application my objects are very related so I don't want to denormalize any further.
I could do a mainroom call, and then do another child call off of that, but I am wondering if I would get dinged twice. in the initial call it would load all messages anyways, and then I would load 30 of them with the child call. Was just hoping someone would have a better recommendation.
Start by reading up about denormalization. This is a concept which is enforced in SQL by table structures, but also important in NoSQL, although you're given enough rope to tangle yourself up and have a bad day.
So the first step is to split messages into its own path:
URL/rooms
URL/messages
Now you can grab your meta data and messages separately, and call limit to set the number loaded:
var fbRef = new Firebase(URL);
var roomRef = fbRef.child('rooms/'+roomId);
var chatRef = fbRef.child('messages/'+roomId).limit(30);
In case you're not convinced that these should be split up, you're going to run into this same issue when you want to create a dropdown containing a list of room names (you have to load all your messages in the current data structure, just to get the room names).
For great justice, split meta data and detailed records into their own paths. Otherwise, all your base are belong to bandwidth.

Meteor subscribe onReady() and observe() added double counted

I want to wait for all data to be downloaded from the subscription and then create map markers for them all at once at the beginning. To do this, I have a session variable set to false. Then when onReady calls, I initialize all the markers. Then I set the session variable true indicating that the first delivery is in and initialized. In my observe callback, I check the session variable and so long as its false, I dont add any markers. Then, if its true, I will add markers -- assuming non of these markers are already initialized. Sometimes, however, I get a double-count and create twice as many markers.
I guess a good first question to ask is what the relationship is between onReady and observe added? Its not terribly clear in the docs. Is this even the correct way of doing things -- creating a session variable to suppress the observe added function until onReady is done? I dont think so. Also note that the double count doesnt happen every time so its a timing thing... kind of annoying.
Thanks
Yes this is the behavior with observe(). When you run observe initially it will have an initial query that will match everything and run into added.
It is also present when onReady hasn't yet fired but the collections are empty at that point so the initial ones aren't visible. This is mentioned in the docs
Before observe returns, added (or addedAt) will be called zero or more times to deliver the initial results of the query.
I'm not sure entirely how to avoid the initial items. I have done something like this in the past:
var QueryHandle = Collection.find().observe({
added: function() {
if(!QueryHandle) return false;
});
I know this works on the server but I'm not certain if it does on the client.
Another solution would be to run the handle before onReady is called and only stop returning if the subscription is complete: i.e
Meteor.subscribe("collection", function() {
subscribed = true;
});
var QueryHandle = Collection.find().observe({
added: function() {
if(!subscribed) return false;
}
);
Be careful not to run this in a Deps.autorun because then it would be run again if the .find() query params are reactive.
This might happen sometimes depending on how fast the server response. If you use Session it becomes a reactive hash so if it happens fast enough that subscribed returns true. Try using an ordinary variable instead.
If its not helpful there might be an alternative way to avoid the initial ones and a deeper level but it might take a dig into the livedata package.

How can I make a reactive array from a Meteor collection?

I want to take a list of item names from a collection as a simple array to use for things like autocompleting user input and checking for duplicates. I would like this list to be reactive so that changes in the data will be reflected in the array. I have tried the following based on the Meteor documentation:
setReactiveArray = (objName, Collection, field) ->
update = ->
context = new Meteor.deps.Context()
context.on_invalidate update
context.run ->
list = Collection.find({},{field: 1}).fetch()
myapp[objName] = _(list).pluck field
update()
Meteor.startup ->
if not app.items?
setReactiveArray('items', Items, 'name')
#set autocomplete using the array
Template.myForm.set_typeahead = ->
Meteor.defer ->
$('[name="item"]').typeahead {source: app.items}
This code seems to work, but it kills my app's load time (takes 5-10 seconds to load on dev/localhost vs. ~1 second without this code). Am I doing something wrong? Is there a better way to accomplish this?
You should be able to use Items.find({},{name: 1}).fetch(), which will return an array of items and is reactive, so it will re-run its enclosing function whenever the query results change, as long as it's called in a reactive context.
For the Template.myForm.set_typeahead helper, you might want to call that query inside the helper itself, store the result in a local variable, and then call Meteor.defer with a function that references that variable. Otherwise I'm not sure that the query will be inside a reactive context when it's called.
Edit: I have updated the code below both because it was fragile, and to put it in context so it's easier to test. I have also added a caution - in most cases, you will want to use #zorlak's or #englandpost's methods (see below).
First of all, kudos to #zorlak for digging up my old question that nobody answered. I have since solved this with a couple of insights gleaned from #David Wihl and will post my own solution. I will hold off on selecting the correct answer until others have a chance to weigh in.
#zorlak's answer solves the autocomplete issue for a single field, but as stated in the question, I was looking for an array that would update reactively, and the autocomplete was just one example of what it would be used for. The advantage of having this array is that it can be used anywhere (not just in template helpers) and that it can be used multiple times in the code without having to re-execute the query (and the _.pluck() that reduces the query to an array). In my case, this array ends up in multiple autocomplete fields as well as validation and other places. It's possible that the advantages I'm putting forth are not significant in most Meteor apps (please leave comments), but this seems like an advantage to me.
To make the array reactive, simply build it inside a Meteor.autorun() callback - it will re-execute any time the target collection changes (but only then, avoiding repetitive queries). This was the key insight I was looking for. Also, using the Template.rendered() callback is cleaner and less of a hack than the set_typeahead template helper I used in the question. The code below uses underscore.js's _.pluck() to extract the array from the collection and uses Twitter bootstrap's $.typeahead() to create the autocomplete.
Updated code: I have edited the code so you can try this with a stock meteor created test environment. Your html will need a line <input id="typeahead" /> in the 'hello' template. #Items has the # sign to make Items available as a global on the console (Meteor 0.6.0 added file-level variable scoping). That way you can enter new items in the console, such as Items.insert({name: "joe"}), but the # is not necessary for the code to work. The other necessary change for standalone use is that the typeahead function now sets the source to a function (->) so that it will query items when activated instead of being set at rendering, which allows it to take advantage of changes to items.
#Items = new Meteor.Collection("items")
items = {}
if Meteor.isClient
Meteor.startup ->
Meteor.autorun ->
items = _(Items.find().fetch()).pluck "name"
console.log items #first result will be empty - see caution below
Template.hello.rendered = ->
$('#typeahead').typeahead {source: -> _(Items.find().fetch()).pluck "name"}
Caution! The array we created is not itself a reactive data source. The reason that the typeahead source: needed to be set to a function -> that returned items is that when Meteor first starts, the code runs before Minimongo has gotten its data from the server, and items is set to an empty array. Minimongo then receives its data, and items is updated You can see this process if you run the above code with the console open: console.log items will log twice if you have any data stored.
Template.x.rendered() calls don't don't set a reactivity context and so won't retrigger due to changes in reactive elements (to check this, pause your code in the debugger and examine Deps.currentComputation -- if it's null, you are not in a reactive context and changes to reactive elements will be ignored). But you might be surprised to learn that your templates and helpers will also not react to items changing -- a template using #each to iterate over items will render empty and never rerender. You could make it act as a reactive source (the simplest way being to store the result with Session.set(), or you can do it yourself), but unless you are doing a very expensive calculation that should be run as seldom as possible, you are better off using #zorlak's or #englandpost's methods. It may seem expensive to have your app querying the database repetitively, but Minimongo is caching the data locally, avoiding the network, so it will be quite fast. Thus in most situations, it's better just to use
Template.hello.rendered = ->
$('#typeahead').typeahead {source: -> _(Items.find().fetch()).pluck "name"}
unless you find that your app is really bogging down.
here is my quick solution for bootstrap typeahead
On client side:
Template.items.rendered = ->
$("input#item").typeahead
source: (query, process) ->
subscription = Meteor.subscribe "autocompleteItems", query, ->
process _(Items.find().fetch()).pluck("name")
subscription.stop() # here may be a bit different logic,
# such as keeping all opened subsriptions until autocomplete
# will be successfully completed and so on
items: 5
On server side:
Meteor.publish "autocompleteItems", (query) ->
Items.find
name: new RegExp(query, "i"),
fields: { name: 1 },
limit: 5
I actually ended up approaching the autocompletion problem completely differently, using client-side code instead of querying servers. I think this is superior because Meteor's data model allows for fast multi-rule searching with custom rendered lists.
https://github.com/mizzao/meteor-autocomplete
Autocompleting users with #, where online users are shown in green:
In the same line, autocompleting something else with metadata and bootstrap icons:
Please fork, pull, and improve!

Resources