Angular-like client side data binding and reactivity with Meteor? - meteor

I'm trying to wrap my head around Meteor's way of dealing with reactivity and I want to make sure I've got some concepts right.
Take the follow reactivity example:
A user types something into a form field. The thing that he is typing is instantly displayed somewhere else on the page, as the user is typing, letter by letter. An instantaneous duplication.
From what I know about Angular, this is a very common example of reactivity. Angular binds the input directly to the output on the client side. There's nothing in between.
Correct me since I could be wrong, but Meteor can do this, but the input would first need to be captured and stored into a Mongo + MiniMongo DB (perhaps only as a collection in local storage), there would need to be a subscribe step, and those values would then be read and displayed on the page.
Is there a way to directly bind an event on the front end to another thing on the front end like Angular does?
Is this right? For Meteor to have the front-end-only reactivity of Angular it must first go through the intermediary of a collection, meaning extra code would be necessary to accomplish this compared to Angular?
The example in the Meteor Docs:
Deps.autorun(function () {
Meteor.subscribe("messages", Session.get("currentRoomId"));
});
So here, when the data of currentRoomId changes, the function is reactive to that data change and the function runs (in this case Meteor subscribes to messages).
Using Session variables is the only way I see of possibly binding two parts of a view together directly. Are there other ways?

Meteor's client-side reactivity system (Deps) is not coupled with its live MongoDB syncing. You can use it with any reactive data source which implements the right interface, including data sources which are entirely client-side. For example, you can use the built-in Session object. This is just a client-side key-value store with support for Meteor's reactivity, and you don't have to do any publish or subscribe to use it.
This standard way to do this sort of thing looks something like this:
<input id="field" value="{{fieldValue}}">
Template.form.fieldValue = function () {
return Session.get("fieldValue");
};
Template.form.events({
"input #field": function (evt) {
Session.set("fieldValue", $(evt.currentTarget).val());
}
});
Now the Session variable fieldValue is synced up to the form field. You can call Session.get("fieldValue") in some helper and that template will re-render when the user types in the form field. And if you call Session.set("fieldValue", "blah") then the form field will update itself.
As for your edit: You can make your own reactive data sources using Deps.Dependency, or you could meteor add reactive-dict although that's not documented. There may be packages on Atmosphere.

Related

How can I use collection.find as a result of a meteor method?

I'm trying to follow the "Use the return value of a Meteor method in a template helper" pattern outlined here, except with collections.
Essentially, I've got something like this going:
(server side)
Meteor.methods({
queryTest: function(selector) {
console.log("In server meteor method...");
return MyCollection.find(selector);
}
});
(client side)
Meteor.call('queryTest', {}, function(error, results) {
console.log("in queryTest client callback...");
queryResult = [];
results.forEach(function(result) {
// massage it into something more useful for display
// and append it to queryResult...
});
Session.set("query-result", queryResult);
});
Template.query_test_template.helpers({
query_test_result: function() {
return Session.get("query-result");
}
});
The problem is, my callback (from Meteor.call) doesn't even get invoked.
If I replace the Method with just 'return "foo"' then the callback does get called. Also, if I add a ".fetch()" to the find, it also displays fine (but is no longer reactive, which breaks everything else).
What gives? Why is the callback not being invoked? I feel like I'm really close and just need the right incantation...
If it at all matters: I was doing all the queries on the client side just fine, but want to experiment with the likes of _ensureIndex and do full text searches, which from what I can tell, are basically only available through server-side method calls (and not in mini-mongo on the client).
EDIT
Ok, so I migrated things publish/subscribe, and overall they're working, but when I try to make it so a session value is the selector, it's not working right. Might be a matter of where I put the "subscribe".
So, I have a publish that takes a parameter "selector" (the intent is to pass in mongo selectors).
On the client, I have subscribe like:
Meteor.subscribe('my-collection-query', Session.get("my-collection-query-filter"));
But it has spotty behaviour. On one article, it recommended putting these on Templates.body.onCreate. That works, but doesn't result in something reactive (i.e. when I change that session value on the console, it doesn't change the displayed value).
So, if I follow the advice on another article, it puts the subscribe right in the relevant helper function of the template that calls on that collection. That works great, but if I have MULTIPLE templates calling into that collection, I have to add the subscribe to every single one of them for it to work.
Neither of these seems like the right thing. I think of "subscribing" as "laying down the pipes and just leaving them there to work", but that may be wrong.
I'll keep reading into the docs. Maybe somewhere, the scope of a subscription is properly explained.
You need to publish your data and subscribe to it in your client.
If you did not remove "autopublish" yet, all what you have will automatically be published. So when you query a collection on client (in a helper method for example), you would get results. This package is useful just for quick development and prototyping, but in a real application it should be removed. You should publish your data according to your app's needs and use cases. (Not all users have to see all data in all use cases)

Meteor - how can I empty out the collection of 10,000 objects I subscribed to after I no longer need it?

I have a template in which a user should be able to click on a button to bring up a modal and in the modal choose a handful of items out of a list of about 10,000 items which are displayed there to search or scroll through.
Since this collection is so big, I don't want to keep it around in memory when I don't absolutely need it.
So I would like to subscribe to this collection only when the modal is being viewed and I would like to ensure that I am unsubscribed if the modal is not being viewed.
Is there a way to explicitly unsubscribe from a collection?
There are a couple of ways you can do this:
Use the subscription handle
subscribe returns a handle you can call stop on. For example:
var handle = Meteor.subscribe('stuff');
handle.stop();
Use an autorun
Because an autorun will automatically start and stop subscriptions when its reactive dependencies change, this will work:
Tracker.autorun(function () {
if (Session.get('showingModal'))
Meteor.subscribe('stuff');
});
Side note - it may make more sense to use a method call for searching such a large data set rather than publishing the entire thing to the client. For example you can set a session variable whenever the user's query changes, then use an autorun to update the result set based on the method's return value.
https://docs.meteor.com/#/full/meteor_subscribe
Quoting the docs :
Meteor.subscribe returns a subscription handle, which is an object
with the following methods:
stop() Cancel the subscription. This will typically result in the
server directing the client to remove the subscription's data from the
client's cache.
So basically what you need to do is storing the subscription handle in a variable and call the stop method when you don't need those published documents anymore.
Note that if you're using iron:router (and you probably should), this is taken care of automatically for you on each route change, which is convenient but has the side effect of provoking a lot of sometimes unnecessary calls to Meteor.publish calls which are non trivial for the server and bandwidth... to address this matter you can use meteorhacks:subs-manager but it's another topic anyway.

In Meteor, how to find which session variable triggers a template rerun

This is a general question about designing meteor applications or debugging meteor applications.
When I write meteor applications, I usually update session variable values to trigger re-runing a template helper function and/or re-rendering a template. So my application has quite a few different session variables.
Sometimes I find that the helper function gets re-run multiple times, but I can't think of any reason why it re-runs so many times. It must be some session variable gets updated and causes the re-run. Is there a way to figure out which session variable causes it?
The general question is: in a reactive design, when I see a template gets re-rendered, how to find why it gets re-rendered?
You could use Deps.autorun to quickly figure out which it is, If you're looking to debug its a quick a gritty way to do
Drop in the code like
Deps.autorun(function() {
Session.get("something");
console.log("Session something has changed");
});
Deps.autorun(function() {
Meteor.user()
console.log("Meteor user has changed");
});
You can place blocks of code like this on your client side to see which is changing. Each one will run once, initially, then after for each time the reactive variable inside it changes.
You would have to do this for each variable you use in your template and it would help you find out which is changing, each Deps.autorun block will run independently only when the variable inside it changes.

Update document in Meteor mini-mongo without updating server collections

In Meteor, I got a collection that the client subscribes to. In some cases, instead of publishing the documents that exists in the collection on the server, I want to send down some bogus data. Now that's fine using the this.added function in the publish.
My problem is that I want to treat the bogus doc as if it were a real document, specifically this gets troublesome when I want to update it. For the real docs I run a RealDocs.update but when doing that on the bogus doc it fails since there is no representation of it on the server (and I'd like to keep it that way).
A collection API that allowed me to pass something like local = true this would be fantastic but I have no idea how difficult that would be to implement and I'm not to fond of modifying the core code.
Right now I'm stuck at either creating a BogusDocs = new Meteor.Collection(null) but that makes populating the Collection more difficult since I have to either hard code fixtures in the client code or use a method to get the data from the server and I have to make sure I call BogusDocs.update instead of RealDocs.update as soon as I'm dealing with bogus data.
Maybe I could actually insert the data on the server and make sure it's removed later, but the data really has nothing to do with the server side collection so I'd rather avoid that.
Any thoughts on how to approach this problem?
After some further investigation (the evented mind site) it turns out that one can modify the local collection without making calls to the server. This is done by running the same methods as you usually would, but on MyCollection._collection instead of just on Collection. MyCollection.update() would thus become MyCollection._collection.update(). So, using a simple wrapper one can pass in the usual arguments to a update call to update the collection as usual (which will try to call the server which in turn will trigger your allow/deny rules) or we can add 'local' as the last argument to only perform the update in the client collection. Something like this should do it.
DocsUpdateWrapper = function() {
var lastIndex = arguments.length -1;
if (arguments[lastIndex] === 'local') {
Docs._collection.update(arguments.slice(0, lastIndex);
} else {
Docs.update(arguments)
}
}
(This could of course be extended to a DocsWrapper that allows for insertion and removals too.)(Didnt try this function yet but it should serve well as an example.)
The biggest benefit of this is imo that we can use the exact same calls to retrieve documents from the local collection, regardless of if they are local or living on the server too. By adding a simple boolean to the doc we can keep track of which documents are only local and which are not (An improved DocsWrapper could check for that bool so we could even omit passing the 'local' argument.) so we know how to update them.
There are some people working on local storage in the browser
https://github.com/awwx/meteor-browser-store
You might be able to adapt some of their ideas to provide "fake" documents.
I would use the transform feature on the collection to make an object that knows what to do with itself (on client). Give it the corruct update method (real/bogus), then call .update rather than a general one.
You can put the code from this.added into the transform process.
You can also set up a local minimongo collection. Insert on callback
#FoundAgents = new Meteor.Collection(null, Agent.transformData )
FoundAgents.remove({})
Meteor.call 'Get_agentsCloseToOffer', me, ping, (err, data) ->
if err
console.log JSON.stringify err,null,2
else
_.each data, (item) ->
FoundAgents.insert item
Maybe this interesting for you as well, I created two examples with native Meteor Local Collections at meteorpad. The first pad shows an example with plain reactive recordset: Sample_Publish_to_Local-Collection. The second will use the collection .observe method to listen to data: Collection.observe().

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