• Meteor
  • Tutorial

Spark Event Annotations

Hello. So, we're continuing, in this episode, on our series on Meteor rendering and the Spark engine. And in the last episode, we talked about the isolate annotation, and I've drawn it out here as a tree so we can see all of the annotations at once. And so, last time we left off with the isolated annotation, and this time we're going to look at events. And specifically, we're going to look at the watch annotation and the events annotation, and how the both of these work together to provide the Meteor event handling system.

So, to start, we're going to look at calling the attach events method of Spark to get a sense of how these two annotations work without all of the other annotations. But then we'll go ahead and use a regular template and see how reactivity and rendering affects the event handling process as well. So, let's get started.

To get us started, I have some boilerplate code that should look similar to what we've seen in the previous Spark annotation videos. Down at the bottom, on line 31, I have the Meteor startup code that just calls append template to body. And if you look up here, all that's doing is calling document.body.appendchild, and then passing in as a parameter the result of calling spark.render with the template function.

And so, that's where we're going to spend most of our time, is in this template function down here. And for now, all it's doing is returning the result of calling HTML func, which is up here. So, again, you can think of this HTML function, or HTML func, as our handlebars template that just returns a string of HTML. In this case, it's just a button that says Click Me. So, as a result of calling append template to body, we can see this HTML rendered to the page up here.

But right now, there's no click event handler on this button, and that's what I'd like to add. Now, you're probably normally used to doing this through the template mechanism by calling template, and then your template name, and then events. But in this video, we're looking at the underlying mechanism of how it works. And so, I'm just going to do it directly on our template function.

To do that, the first thing I'm going to do is change the startup code. And instead of appending the template to the body, I'm going to print out the annotations to the console so that we can see them. So, I'll pass in the print template annotations method to the startup function. And then, over on the right, we can see in the browser that, since we haven't actually added any annotations, our annotated HTML is just our button HTML string. So, let's add some event annotations now.

So, to do that, inside the template function, instead of just returning the results of calling HTML function, what I'm going to do is return the result of calling spark.attachevents. And as the first parameter, I'm going to pass in the event map, which we'll do in a minute. And as the second, it'll be the result of calling HTML function.

So, over on the right, we can see now that we have some annotated HTML. And we haven't actually added any events yet, but we still get the annotations. And so, on the inside here, you can see the button HTML. And then, right around that, we have a watch annotation. And then, around that, we have the events annotation.

And we'll look at these in more detail in a minute. But first, let's add an actual event.

So, inside my event map, this will look just like what we-- if you've used templates before-- what the event map looks like when you call your template events method and pass in the events map. So, in this case, what I want to do is handle the click event on the button. And then, the value here will be event handler. So, that'll be a function which takes the event object as a first parameter, and then a template, if we had one, as the second. And so, for now, what I'll do is just say the click event was handled, and then print out the event object so we can see it.

So now, instead of printing the annotations, let's just render the button to the body. So, I'll call append template to body in the startup method. And then, over on the right, if we click the button, we can see that the click event has been wired up. And the first parameter that we get on a click is the mouse event that comes from the browser.

Before we look at an example of using events with a Meteor template-- just a regular template, like what you would use in your projects-- I want to spend a little bit of time in the spark.attachevents source code. In case you're following along, I'm in the Spark package, and I'm looking at spark.js. And on line 723, you see the definition of spark.attachevents. And I'm not going to spend a ton of time in here, but I just want to highlight two parts of this code so that, when we go back to the browser and we trace through what's happening, you can kind of remember where this was in the source code.

So, the two things we want to look at are the two annotations that are applied in this method. So, the first one is down here on line 757. And you can see that this is where the watch annotation is applied. And it's doing something sort of odd here with adding and passing an object as the third parameter. And that object has a notify method that calls install handlers. We'll come back to that in a little bit.

But then, down here, we have another annotation being applied, and this is that events annotation that we saw before. And this looks a little bit more like what we've been playing with, where I pass a material virtualization function as the third parameter. And that function just takes a live range as its parameter.

So, one last thing to look at here, before we go back to the browser, is most of the work is happening inside of the events annotation materialization function. So, you can see here, for example, that Meteor is adding the event handlers to the DOM. And then, down here, you can see the handling logic for dealing with a click event, for example, and where our callbacks end up getting called. And so, I think the best way to understand what's going on here-- and also, what the difference between install handlers being called in this notify method versus down here-- I think the best way to understand that is just to see it in the DOM. So, let's go do that.

I've swapped out the code here so that we can look at how events work with just a regular Meteor template. So, up on top, you can see I've created a template called greeting button. Let me get my pen. I've created a template called greeting button. And inside it, I just have to regular button tag with a special helper.

So, instead of just writing, Click Me, I actually want to grab this value from a helper. And if I look at what that helper does, it just calls-- it returns the value of session.get. And so, whatever this is set to, the button text-- the session key is set to-- is what's going to show up here. And obviously, you can see that I've initialized it to the string Click Me.

But what this is going to do is it's going to make this template reactive so that, if this value changes, we would expect this template to be re-rendered. And so, we're doing this because we want to see the effect that has on the event handling mechanisms. And then, up here in the body, you can see that I'm not actually rendering the template. I'm going to do that manually down here. And so, in the JavaScript you can see at meteor.startup, I passed the rendered reading button method.

So, I want to get a sense of what's happening. And the way to do that is I wrote a bunch of logger methods that allow us to visually see what's happening, when different annotation methods, and so on, are called. So, to see those what I'm going to do is just uncomment the show logger. And you could look at the source code and GitHub if you want to dig through it. And let me expand the browser so that we can get a closer look.

So, up top, we can see our initial render of the template. So, you can see the Click Me button is in the DOM. And we haven't done any reactivity yet. We haven't changed the session variable. So, this is just the result of appending this template to the body. And then, down at the bottom, you can see some logging output that I created. So, we can trace through the different methods that we're interested in, and what order they're called.

So, the first two we can basically ignore. These are just in the startup function that we called, passing in the render greeting button to kick off-- a function to kick off our rendering process. And then, the first isolate method that's called is just for the body, the empty body. So, down here, we're looking at the methods that are called for our template rendering itself.

So, as we can expect, the first method that's called is the greeting button template itself. And then, within that, we call spark.isolate, like we saw in the last video, which gives us reactivity, and then we call the attach events method. And we pass in the event map as the first parameter. And as the second, we pass in the annotated HTML so far. So, as you can see, we have the button HTML in the middle, and around it we see the isolate tag because we just called the isolate method.

And then, down below-- let me clear this out a little. Down below, we see that, within attach events, the add type method is getting called, and the install handler method is getting called. Let me come back to that in a minute, once we've gone through all this, because this is a little bit tricky.

And then we see that the rendered callback is called. In the rendered callback is the same one that you would use by saying template.greetingbutton.rendered equals function. So, we know, at this point, the template is in the DOM. So, we should be able to see it at this point up here. And then, after it's in the DOM, you can see that this install handler method is called again. And so, that's what I want to talk about for the next couple of seconds as to what's happening here.

So, I think, to do that, the first thing to understand is, what is a universal event lister? Well, this is, in short, Meteor's way of normalizing all the different browser differences that there are for event handling, and namely, the differences between the W3C model of handling events and Internet Explorer's way of handling events. And so, this normalizes the event handling systems, and then adds some other goodies to that as well.

So, when we called add type on the universal event listener, this is actually applying a global event type to the entire document. And so, every time that there's a click event that happens, Meteor is smart about it and says, OK, a click happened somewhere up here. Now, does it apply to this template and this button? And if so, then, call the callback.

So, every time there's a re-rendering, we don't need to add this type again. We only need to do it once. And so, this actually happens during materialization inside of the events annotation materialization function. So, we just saw that in the source code previously.

Now, install handler is a specialized way of dealing with IE6 through 8. And what this does is it just calls the other way of handling events. And so, this all happens inside the events annotation materialization function that we just saw.

But then, the DOM actually-- the node gets rendered and put into the DOM. And so, we see the rendered callback is called here. And then, the install handler method gets called again.

And the reason is that-- do you remember the watch annotation? Well, the watch annotation-- what that does is it added that notify method to the live range, and that notify method gets called every time there's a re-rendering. And every time there's a re-rendering, what that function does is it calls install handler.

And the reason is that on IE6 through 8, if there's a re-rendering, we actually have to go through and add the event handlers to the DOM again, to each individual node. And that's why we have to call install handler on a re-render. But if we're using another browser, this line alone is enough to handle the events, even if we re-render the DOM nodes. So, to get a sense of this, let's just change that reactive variable and see what happens. And that'll be enough, I think, for this video.

So, if I say session.set "buttontext," and we'll change it to 2. You can see, up here, the buttons change as we would expect. But this time, not all of this was called again-- just a little bit. So, just these two things were called.

So, first, the rendered callback gets called, as we would expect. Because we've re-rendered some HTML and added it to the DOM because of our isolate annotation. And then, the install handler gets called again because of the watch annotation. And so, this gives IE6 through 8 a chance to-- it gives us a chance to attach the event handlers again to the new nodes that are in the DOM because of those browsers' incompatibilities with the newer W3C model.

So, we can do this just one more time, just in case it wasn't clear. Buttontext, 3. And again, you can see that the template is re-rendered to the DOM. And so, we get our render callback called, and then the install Handler method is called again because of the watch annotation.

So, this was quite a bit of material to cover in one video. And I hope it was clear enough. But once we're done with all the rest of the annotation understanding the rest of the annotation functions, we can dive into the universal event listener and understanding that in more detail if people are interested.