Detect template change in Meteor - meteor

I'm looking to make an element flash on screen when the underlying collection is updated.
It seems to me that it would make to have an equivalent of Template.my_template.rendered = function(){} which is fired every time the template is updated.
Ideally, this function would not fire the first time the template is rendered.
This is seems like such an obvious use case, am I missing something?

For Meteor < 0.8
You should use Template.myTemplate.created to do something for the very first time. From the docs Template.myTemplate.rendered is fired everytime there is a change including the first time. If you want to limit it to only the second time and changes after that (I'm guessing this is what you want), you have to write some custom logic. Currently there is no Meteor api that supports that.
For Meteor-0.8
It seems like this API underwent some backwards incompatible changes in Meteor-0.8.
There is an elegant way to achieve this as described Adaptation to the new Meteor rendered callback here. Bascially you should modify whatever function you are attaching to the variables inside your Template's javascript by calling a helper function once. For example,
var renderCount = 1;
var myHelper = function () {
console.log("rendered #" + renderCount);
renderCount++;
};
Template.myTemplate.myVariable = function () {
myHelper();
return this.name;
};
Hope this helps. There is also another alternative approach in that same repo. You might like that one.

There is no simple solution; more discussion here: https://groups.google.com/forum/#!topic/meteor-talk/iQ37mTP3hLg

Related

switch fullcalendar options on window resize without destroy

we built a calendar using fullcalendar.io and have an issue on that.
If the container size gets smaller than 500px we want to reload the calendar using different options: the events should look a bit different, the click on events should behave different and so on. So we created the two different option sets and in the windowResize option we check, if the calendar should use the one or the other set. To force a re-init we use destroy:
windowResize: function(view) {
if(self.containerIsLarge()) {
self.$container.fullCalendar('destroy');
self.createLargeCalendar();
return;
}
},
Unfortunately we get some other issues then, that we are not really able to solve in a smart way (we work in an environment where there is an incompatible version of moment.js so we get trouble after destroying the calendar since it initializes with the incompatible moment.js and doesn't really work at all ...).
Is there a way to completely switch options and reload the calendar without destroying it before? I know about the on/off feature and the option setter but I didn't get it to work properly. Is this the correct way or did I miss something ... ?
Thanks in advance!
Regards
Manuel
Okay, after some hours of research I found a working solution.
1) remove all event callbacks that differ depending on the containers size (this is the alternative to 'destroy' I was looking for):
var calendar = self.$container.fullCalendar('getCalendar');
calendar.off('viewRender');
calendar.off('eventRender');
calendar.off('eventAfterAllRender');
calendar.off('eventClick');
calendar.off('eventLimitClick');
calendar.off('eventLimitText');
2) set all event callbacks and options needed now:
self.$container.fullCalendar('option', 'eventLimit', true);
calendar.on('viewRender', function (view, element) {
...
});
calendar.on('eventRender', function (event, element, view) {
...
});
calendar.on('eventLimitClick', function (cellInfo, jsEvent) {
...
});
...
3) trigger events and therefore run the new callback functions:
self.$container.fullCalendar('refetchEvents');
self.$container.fullCalendar('getView').triggerRender();
Regards
Manuel

Meteor - Making DOM Changes from Helpers

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.

Correctly stopping an autorun() function when template is destroyed

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.

Can I subscribe to Meteor Session for reactive template render updates?

Is there a way to subscribe to the Meteor Session object so that a reactive template view automatically renders when data is .set on the Session object? Specifically the key/name and value data?
I have a similar question related to rendering Meteor Session object data when iterated over. This question is specifically different on purpose. I want to get an answer out on an alternate way and possibly better way to do the same thing.
I do not want to have to call Session.get('name'); This use case is because I don't know the names in the Session object.
So, I would like to be able to have something in handlebars that allows me to
Psuedo code...
{{#each Session}}
{{this.key}} {{this.value}}
{{/each}}
Unsure about subscribing to the session, but for the second part of your question, you can use this:
Template.hello.session = function () {
map = []
for (prop in Session.keys) {
map.push({key: prop, value: Session.get(prop)})
}
return map
}
then in your template:
{{#each session}}
{{key}} {{value}}
{{/each}}
Not sure if there's a more elegant way but it works.
I don't think so.
Have a look at https://github.com/meteor/meteor/blob/master/packages/session/session.js. It's actually reasonably simple. The invalidation happens in lines 45-47. You'll see calling Session.set invalidates anyone listening to that key specifically (via Session.get) or to the new or old value (via Session.equals). Nothing in there about invalidating the session as whole.
On the other hand, considering how simple it is, it wouldn't be super hard to write your own data structure that does what you want. I'm not sure what your use case is, but it might make a lot of sense to separate it from the session.
Yes you can subscribe to Session values.
Take a look a the docs for autosubscribe:
http://docs.meteor.com/#meteor_autosubscribe
// Subscribe to the chat messages in the current room. Automatically
// update the subscription whenever the current room changes.
Meteor.autosubscribe(function () {
Meteor.subscribe("chat", {room: Session.get("current-room");});
});
This code block shows one way to use this. Basically any time the value of "current-room" is changed Meteor will update the views.
EDIT
I misunderstood the initial question and decided I need to redeem myself somewhat. I just did some tests and as far as I can tell you can currently only subscribe to collection.find() and session.get() calls. So you can't subscribe to the whole session object or even to the keys object.
However, you can set a Session value to an object this may not be the most elegant solution but this worked for me on keeping track of the keys object with some hackery to keep from getting a circular object error.
var mySession = {
set: function(key, val){
var keys = Session.keys,
map = {};
Session.set(key, val);
for (var prop in keys) {
if(!(prop === "keys")){
map[prop] = keys[prop];
}
}
Session.set('keys', map);
}
};
This gives you something that looks a lot like original functionality and can help you keep track and update templates as you add or change Session values.
Here's my template helper (borrowed from previous answer):
Template.hello.keys = function() {
map = [];
keys = Session.get('keys');
for (var prop in keys) {
map.push({key:prop, value:keys[prop]});
}
return map
};
And here's the actual template:
<template name="hello">
<div class="hello">
{{#each keys}}
{{key}} {{value}} <br />
{{/each}}
</div>
</template>
This way you can just call mySession.set("thing", "another thing") and it will update on screen. I am new to Javascript so someone please let me know if I'm missing something obvious here, or let me know if there is a more elegant way of doing this.
While accessing the session object in this manner is doable .. You're not drinking the cool-aid.
In other words, Meteor excels in the respect of having publish/subscribe. Its not clear (to me) how much data you've got to put into the Session object before a browser crashes; I would rather not find out.
You would merely put all your session dependent code in a Deps.autorun() function to plug into subscriptions as mentioned in an earlier answer. Any time a session changes it'll modify the subscription; you can attach a ready callback, or use subscription.ready() checks to initiate specific actions, but ideally you'd structure your templates in a way that you could use rendered/destroyed functions, taking care to use {{isolate}}/{{constant}} when possible.
What you're getting at is kind of a difficult way to do something simple; and may not adhere to changes in meteor in the future; we can always be assured publish/subscribe will function as expected... Given Session is such a simple object.. whats to say someone designs something better shortly.. and it gets merged into meteor; and you're left with some weird workflow based on custom objects that may not break the Session; but may impact other parts of your interface.

Googlemaps return object from initialize function

I have a list of infowindows lets say numbered 1 to 5.
and they have a form to get directions. When I draw the route the infowindow should colapse. Everything works, I just want the infowindow object available outside the function
infowindow.close() works fine if its in initialize, however the function calcRoute() is outside of the initialize function. I'm trying to return the infowindow from the function but not sure where I am going wrong
here is the jest of what I'm doing
function initialize() {
........
var infowindow3 = new google.maps.InfoWindow({
content: iwindow('marker3','st pete','27.81884967015435','-82.65828121138918','727-822','4011 mlk','Fl','33703','201'),
maxWidth: 300
});
.........
return infowindow3;
}
// I have tried alerting every combination of this, window and even the function name
alert(this.window.infowindow3);
// if it would alert ObjectObject I would have it.
I know I'm close and hopefully someone familiar with the maps can shed some light.
A big thank you in advance. (I have spent days trying to get this one)
This may be a stupid question, but are you saving the return value of initialize? Of course you are not showing your whole code, but that's not clear from what is there. Something like this:
var infowindow3 = initialize();
alert(infowindow3);
Of course, the typical way to call initialize is via the load event, so you wouldn't be able to get the return value that way. So you would set up a (in your example) global variable, and that set that global variable inside of initialize (and not returning anything).
Update:
Ok, after looking through the page you linked to (in your comment) I see that you are calling the alert(infowindow3); before infowindow3 gets defined. You can also see that in the page itself, the alert pops up with "undefined" and the page isn't loaded yet - you can't see any map and consequently initialize() hasn't run yet. Bear in mind that during a page load, any script outside of a function (or a function's code if the function call is in that "open space") will get executed the moment the browser reads it. This alert is an example of that. You need to put that alert (or any code that uses this variable) in a function that does not get called until it is defined by initialize(). Examples of this are functions that get called with onclick. In fact, initialize itself is an example of that (it gets called with onload).

Resources