• Meteor
  • Tutorial

Spark Isolate Annotation

In the last episode, we talked about Spark annotations in detail. And we looked at how annotations let us mark up some HTML like this button with different annotation tags and then attach functionality to the HTML based on the tags. So in the browser here I have an example template that, as you can see, is just a button with a Click Me label here. And then all around it, I have these different annotations that Meteor has attached automatically.

And so what we're going to do over the next couple of videos is work our way from the inside out and look at each one of these annotations separately. So today we're going to talk about the isolate annotation. And this is one of the coolest ones, I think, and the one that you're probably most familiar with. Because it's what gives us the reactivity of the user interface.

Before we dig into the isolate annotation, let's just make sure that we understand what's happening with annotation functions by reviewing the process one more time. But this time, we'll just look at it from a slightly different angle. So you can see that I have some HTML here. It's just an h1 tag that says I'm in the DOM.

And then around it, I've created two custom annotations. The first one is just called first-annotation, and then the second one is called second-annotation. So the first step in the rendering process, which we've talked about, is to annotate our HTML with some annotations.

And then the second one, the second step, is to materialize that string of HTML with the annotations into a DOM fragment. And in that process, we call the materialization functions for each one of these annotations that Spark finds. So what you can see here is that in my custom annotation materialization functions, I just print out to the console when it gets called.

So the thing to notice here is the first one to get called says materialized first annotation. And then it says materialized second annotation. And so Spark is working its way from the inside out and calling these functions one by one.

Then the third step is to return a document fragment that has all the functionality that was created during materialization attached to the fragment and its various nodes. So then finally we take that fragment, and we append it to the DOM. And that's what we see up here.

OK, so let's look at that JavaScript quickly so that we can visualize that process from code. OK, I've opened up my editor so that we can take a look at what this looks like in code. And focus your attention to start down on line 44, the printSampleAnnotations.

So the first thing that we'll look at is we have this HTML function. And that HTML function just returns some HTML. And so this is where we get the h1 tag that says I'm in the DOM. And we see that in the center here.

And then I have a function called buildAnnotations. And this might look similar to what you would see in Meteor's deftemplate file in the templates package. And what you'll see is that it just returns a series of functions that take functions as parameters. And so let's look at it for a second.

So the first thing I do is I call the second-annotation function. And as a parameter, I pass another function. And that function just returns the first annotation and as a parameter passes the HTML function.

And so in the innermost function, which is my HTML function, you see that's what shows up in the center. And then when you go out a level here to first-annotation, you see that that's this function here. And then the outer function is the outermost annotation.

And then as you can see and hopefully remember from the last video, in the annotation functions themselves, all I do is I grab the renderer and then call the annotation method on the renderer with the name of the annotation, passing in whatever function was passed as a parameter, calling that function, and getting the HTML string back from it. And then during the materialization process, I called the materialized method. And that's what causes these strings here to get printed to the console.

So essentially we just have some nesting of functions. And that's what you're going to see in annotation code. So with that said, let's jump into looking at the Spark.isolate method and see how reactivity works.

OK, I've swapped out the code for the code that we'll be using to look at the isolate annotation. So let me walk through it so we know what's going on. Down at the bottom on line 57, I have the Meteor.startup method. And I'm just passing it the renderWithIsolateAnnotation method, which is up here.

And the first thing I'm doing is creating an HTML function. And the HTML function is just going to grab the value from this ReactiveData object, which we'll look at in a minute, and then returning some HTML. So it'll be an h1 tag plus whatever value it gets from the data and the closing tag.

And so then I have a template function here. And for now, it's just going to return the result of calling the HTML function. And then I just go ahead and print the annotations like we've done before so we can get a look at it in the console. And so over here you can see that I haven't actually created any annotations yet. So I'm just seeing the HTML string itself returned with the value of a reactive data, which right now is set to EventedMind.

OK, let's take a look at the ReactiveData object. And if you haven't seen the video on reactivity with context, it's the first one in the list. And so after you watch this, you might want to go back and watch that to get a better understanding of how context and context sets work.

But let me summarize. What we want is if this value changes, we want to be able to automatically re-run some functions. And so what you see here in the set method is that we call this invalidateAll in our context set. And under the covers, that's just saying, go and re-run some of the functions that are dependent upon this value.

And how do I know which context or which functions are dependent upon this value? Well, in the get method, I say this._contexts.addCurrentContext. So that stores a reference to the context in which we get this value. And then I just return the value.

So down here in my HTML function, I call the get method. And so this context or this function will be re-run if that value changes. So let's go over into-- I'm sorry, let's jump down to line 54 and clear this out. And instead of printing the annotations, I'm going to render them to the DOM. I'm going to render the template, rather, to the body. I had to check the method name.

So I'm going to call renderTemplateToBody, and I'll just pass in the function as a parameter. OK, so we have the EventedMind value printed up there at the top. And what I want, of course, is when I say ReactiveData.set, I want that HTML to update automatically. I want the function that renders this string to the DOM to update.

And so let's do that with a Spark.isolate notation. So I'm going to modify my template function. And instead of just returning the result of calling htmlFunc, what I'm going to do is return the result of calling Spark.isolate. And Spark.isolate takes as a parameter, just like any other annotation function, a function.

And then we are expected to return some HTML from this. So what I'll do is I'll return the result of calling htmlFunc within this isolation method. Now, this is sort of a longhand way of basically writing return Spark.isolate and then just passing the function itself. Because Spark.isolate is just going to return the result of calling this function.

So then instead of rendering it right to the body, let's just make sure we can see the annotation. OK, so I've uncommented out the printAnnotations method so that we can take a look at it in the browser. And over in the browser, you can see in the middle I have my HTML with the value of the ReactiveData source being EventedMind. And then surrounding it, I have the isolate annotation tags. And what the isolate annotation is going to do is it's going to say, make this function here reactive so that if it relies on reactive data sources, like ReactiveData, and those values change-- in other words, the contexts are invalidated-- essentially re-run this function.

So let's get a sense of that by rendering this template to the body. OK, so here again we see EventedMind rendered into the body. But if I say ReactiveData.set to something else, the value is changed automatically. And so that's what Spark.isolate buys us.

So there's a couple of interesting things that we should look at before we conclude. One of those is you know that we're not appending the HTML to the DOM again like we did initially. Because otherwise we would just see duplicates of the HTML printed over and over again. And so Spark is actually smart about it. And if the HTML has already been put into the DOM, it finds it and then replaces it with the new value. And so that's some of the extra functionality that we get with Spark.isolate.

And so before we conclude, let's actually just step through that in the debugger so we make sure we understand what's going on. OK, let's step through the debugger and see what's actually happening when a reactive data source changes and the DOM updates.

There's quite a bit going on here. So the point is not to understand every last piece of code but more to visualize what's happening. So to do that, what I'm going to do is to refresh the browser. And we'll watch the initial rendering process.

So here we are in the HTML function that we created. And if I press Play, we get the EventedMind h1 tag added to the DOM. So this is part of the initial append to body rendering process that we've been talking about all along.

But let me jump down to the console now and type ReactiveData, and we'll set a new value. OK, so here we are again in the HTML function. And this is the reactivity that we've been talking about. And so that should make you think that the function is being run again. And so we'll grab the new value for data. And if I press Play, that'll drop me to the next breakpoint I created.

And what Spark is going to do, it's going to patch up the DOM with the new value. And so if I hit Play again here-- and let's step through this a little bit more slowly. You can see I'm in this private method called replaceNodes. OK, so watch carefully. The first thing that happened is the old node is removed. And so now there's actually nothing in the DOM.

But if I keep stepping forward in the debugger, the final step is to insert the new value. And so now I see the new value up here in the DOM. And so the cool part about this is that I'm not just appending new document fragments to the document object model or the DOM. Spark is actually going through and patching up the DOM as it already exists if that HTML has already been rendered for the first time. And so I hope that this video gave you a better sense of what the isolate annotation function is doing.