Transcript
  • Meteor
  • Tutorial

The UI.Each Component

In the new Meteor UI package, the Each Helper is now also implemented as a UI component. This means that when we use the Each Helper, just like with any other user interface component, Meteor UI will create an instance of the component and render it into the page. So this allows us to do some cool things, like we can extend the UI component and adjust its behavior that way.

In this video, we'll look at the implementation of UI Each. You can find it over in the UI package. And the component is off of the UI namespace. And it's UI.each. Just like with the if, unless, and with conditional components, the UI Each is mapped automatically to the Each Handlebars' block helper.

Now, the other components that we've looked at so far are in the components.js file, and the Each component is in its own file. And probably the reason for that is because Each is a pretty hefty implementation, and so it was deserving of its own file. So let's dig into the implementation of the UI Each component.

To make things easier to digest, on the right, I've opened up my applications code-- the HTML file-- so that we can see the each handlebars or template expression. And over on the left is the implementation of the component class itself. The first thing to note is in the initialization function. Again, this is the function that gets called when this component is initialized. And that happens when a new instance of it is created and we're about to render it into the dom.

So inside the Init function, the first thing that happens is a property called Sequence is assigned to this.data. And so this.data is going to be set to the first parameter here, the first positional argument. In this case, it's called items. So that could be a cursor, or it could also be an array.

Now, previously, if it was an array, we wouldn't get any reactivity. We could only get reactivity if we used a cursor. But in the new Meteor UI implementation-- you'll see in a second why this is the case-- we can also use arrays as the data context. And so then the data context is set to undefined. And apparently, that's so that we can use the dot dot notation and skip over this data context for this component.

Now let's take a look at the render function. You can see the render function now takes a parameter called Mode Hand. And I suspect that may change in the next couple of months. But the idea here is that you can pass a mode hand which will tell the render function whether it should render statically-- like for example, if you're on the server-- or dynamically with reactivity and all of that, like if you're on the client.

Now, in the first couple of lines on the render function, you'll see this looks pretty similar to the if, unless, and with components. So the first thing we do is assign self to this, and then the content variable gets assigned to self__content. And that's all the stuff that's inside of the block. We didn't put an else statement in here, but just like with the conditional helpers, if items is falsely or it's empty array, the stuff inside of the else block will get rendered. And that's what else content is set to here.

Now, the render function of the each component is a little bit strange in that unless than mode hint is static-- in other words, whoever called the render function of the component doesn't care about any reactivity-- then the render function will just return null. And then all the magic will happen in this parented method, which we'll look at in a second. But if the mode hint is static-- and remember, that's just a parameter that was passed into the render method-- in that case, it'll call the fetch method of something called observe sequence. And we haven't talked about observe sequence. And we'll look at it specifically in another video.

But what observe sequence does is it lets us wrap either a cursor or just even a regular array and then observe it. And so it adds the benefit of being able to observe any array like object, as before, we could really only observe cursors. And so what this is going to do is call fetch on the observe sequence. And as a parameter, it will grab the sequence. And remember, the sequence is the data context that was passed in here.

So in this case, items is a cursor, but it could be an array. And what it'll do is grab an array form of this observe sequence and then map over all of those items returning the content block with a particular data context. And that data context will be the item itself. And then if the parts.length is greater than zero, then it'll return all of those content parts. Otherwise, it'll return the else content.

Now, most of the time, when we're using the Each component in the client, we'll be returning null from the Rendered function. And all the magic will happen in this Parented function. Now, the Parented function gets called when the Each component has a parent in the DOM. And so let's take a look at how parented is implemented.

The first thing that happens is that Self gets assigned to the component instance itself. And then it grabs a reference to the DOM, which is a DOM range instance assigned to the component. And we'll keep this range so that we can materialize different things into it later. And then we grab a reference to content and else content. And again, the content is the stuff inside here, and the Else content would be in the Else block.

Then scrolling down a little bit, there's another variable called Item Count. And the item count just keeps track of how many items are in our list. And if it's zero, then the else content gets rendered. And if it's greater than 0, then the individual items get rendered.

Now, the really cool stuff happens down here in the observe sequence observe method. So notice that we create this observe handle and assign it to observe sequence observe. And as a first parameter to observe, this function passes a function, which returns the result of calling a Get sequence. And so this will grab the array or the cursor that is stored on the component instance.

But notice that it's a function. And the reason that it's a function is because that makes it reactive. And so Observe sequence can take a function that returns a reactive data source value. And that way, if we pass an array, for example, and that array changes, observe sequence can automatically calculate the dif from that. So that's pretty neat.

And then as a second parameter, we get this object, and we can pass in a key value map of all the different operations that can happen from an observe. So the first one here is [INAUDIBLE]. And so when a new item is added to the list-- this call is the Add to Count method, and we'll take a look at that in a second. But then the important part is down here. And so what this is going to do is it's going to call UI render and then use the content block, which is the stuff inside here, and create a new instance of that by calling with data and passing in the data context function. And as a parent component, it will pass itself.

And if you look at the delta function, it's just a function that gets created anonymously when this function [INAUDIBLE] is called. And what it does is it creates a data dependency and then returns the data value. And then this dollar sign set method gets added to the data function and allows us to call this method with a new value at a later point. And then we can invalidate the dependency.

Now, the counterpart to this is down here in the changed callback. Inside of this function, if a data item changes in our cursor or an array, then we can use the saved range variable, call Get to get the component instance itself by the ID of the item, then grab the data property and call the set function on it with the new item's value. And all of this is automatically reactive. And then above, you can see the implementations for removed and move to, which are a little less interesting, but you can see calls being made into the DOM range to remove the item or to move it. And then finally, down in the destroyed callback, the Observe handle get stopped if it exists.

One last thing to show-- if I scroll back up and look at the Add to Count function that gets called from the various observe sequence callbacks, you can see what it does is if the item count ends up being 0, it can call the UI Materialize function again, which is pretty neat, passing in the else content as a parameter, and then the DOM range that we saved away before. And so we can render in the Else content right into the Dom range, replacing anything that was there before.

So there's quite a bit going on in the implementation of this component, but I think it's a lot saner than the previous implementation from the Spark days. And some of the key benefits that we get from this new implementation is that since Each is a component, we can extend it, just like any other component. Another benefit is that under the hood, it uses something called an Observe sequence from the Observe Sequence package. And that allows us to use an array or a cursor. And either way, both of those will be observable.

So I hope this video gave you a better sense of how the Each UI component is implemented.