Is it a good practice to make DOM changes from Meteor helpers? I'm currently relying on a javascript function to run inside the Meteor helper which makes the function run every time a collection data change occurs.
I know there is Tracker.autorun() but as far as I know, Tracker.autorun() only works for Session variables and does not work for collection data changes.
My current ways so far has not failed me or caused any problems but I'm not 100% sure if this was how Meteor was meant to be used.
Code Example
Template.page_body.helpers({
orange: function() {
do_some_rand_function()
return this.name
}
})
This code will make sure that do_some_rand_function() is ran every time this.name changes (this.name is a variable gotten from a Mongo Collection, therefore it is reactive).
No. Helpers should not have side effects (such as manually updating your DOM, modifying the database, making an HTTP request, etc.).
Your description sounds like a good use case for adding a template autorun in the rendered callback. All autoruns are reactive computations so they will rerun if any reactive variable used within them changes (Session, Meteor.user, Collections, etc.).
Give something like this a try:
Template.myTemplate.onRendered(function() {
this.autorun(function() {
if (MyCollection.findOne()) {
do_some_rand_function();
}
});
});
Also note that template autoruns are automatically stopped when the template is destroyed.
Related
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)
I believe the issue is with the routing, but I may be wrong here.
I have an issue where a function that I call via Iron Router sometimes doesn't fully finish making necessary computations before the template renders and the page is filled with n/a's and NaNs (because of the calculations not finishing before the template loads or renders). So what happens is after the user logs in, he/she uploads data via external file. This data is then stystematically added to an existing MongoDB collection then calculations are being made by function computeValues().
My problem, as I've stated above, is that there are times where the computeValues() function doesn't completely finish making necessary calculations, but the template renders anyway. When I refresh this page, the process works as it's supposed to. It's only when I'm routed from one page to this particular template/page (called mainPage) when this issue exists. Then, there are other instances where it works like it's supposed to and the template doesn't render before the function executes and computers the data completely.
Here is the particular code for the Iron Router:
Router.route("/analyze/main", {name: "main",
subscriptions: function(){
return Meteor.subscribe("data");
},
onBeforeAction: function(){
if(! Meteor.user()){
this.layout("accessDenied");
} else{
this.next();
}},
action: function(){
this.wait(computeValues());
if(this.ready()){
this.layout("mainPage");
} else{
this.layout("loading");
}
}});
Here is what I added to the template's onRendered() function:
Template.mainPage.onRendered(function(){
computeValues();
});
Adding or removing the onRendered part for the template makes no reason. The problems still persist from time to time. I just can't figure out what's causing this or how to fix it.
The particular function that does all the calculations, computeValues(), is located in the lib folder due to the need to expedite the loading process and have access to global variables immediately. I can't post the code because it's over 3000 lines long. But the extensiveness of this function does not explain why it works 20% of the time when routed, and not working 80% of the time, all while working 100% of the time being on the mainPage template and refreshing for reload.
You could use IR to waitOn one or more subscriptions but it's not quite as well suited to waiting on set of computations.
Firstly I would move computeValues() to Template.mainPage.onCreated rather than onRendered
Secondly I would set a session variable to false when starting the computation and to true when finished and show a spinner in the template until it becomes true.
I have a template that contains a chart, rendered using MorrisJS. The chart should update when the currentData session variable is changed, so I have made it a reactive data source with:
Template.chart.rendered = function() {
var template = this;
Deps.autorun(function(c) {
// Stop if the template is removed from the dom
// Q: Is this really right?
if(template.__component__.dom.parentNode() === null) {
c.stop();
return;
}
var results = Session.get('currentData');
// ... render a chart with `results` as the data
Morris.Bar({element: template.$(".chart-container"), data: results, ...});
});
};
Notice how I do a fairly horrid check for when to stop the autorun above. This was necessary because without this, when I navigate away from the page using the template (I'm using iron-router) to another page and back, I get warnings in the log like "Can't select in removed DomRange". I'm pretty sure this is happening because the template instance is removed, but the autorun is still running.
I feel like I'm doing something wrong here, though. Is there (a) a better place to put the autorun so that it doesn't have this problem or (b) a better way to stop the computation when the template instance is removed from the DOM?
I tried to find a way to do it with created and destroyed handlers, but I couldn't figure out how.
Tracker.autorun returns a handle that you can store as a template instance property, then call its stop method in the onDestroyed lifecycle event.
Template.chart.onRendered(function(){
this.computation = Tracker.autorun(function(){...});
});
Template.chart.onDestroyed(function(){
this.computation.stop();
});
EDIT 29-09-2014
In newer versions of Meteor (0.9 onward), there is a new autorun function available on template instances which provide simpler code to achieve the same result : no need to store and stop the computation manually, this is taken care of by the framework.
Template.chart.onRendered(function(){
this.autorun(function(){...});
});
With the new autorun
Template.chart.onRendered(function(){
this.autorun(function(computation){
...
computation.stop();
});
});
but with this autorun, when chart template is removed from the DOM it is removed automatically.
This is in Meteor documentation here:
The Computation is automatically stopped when the template is destroyed.
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.
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.