How does Spacebars {{#each}} work?

492 views Asked by At

I do not understand what happens with the Spacebars {{#each}} template tag.
For example I have a simple event code:

'click #player': function(){
  var playerId = this._id;
  Session.set('selectedPlayer', playerId);
  Session.set('selectedPlayerName', this.name);
}

And this corresponding code in the template:

{{#each player}}
    <a href="#" id="player" class="{{selectedClass}}"></a>
{{/each}}

And finally this helper:

'selectedClass': function(){
  return this._id
}

How do the event handler and the helper function knows which of the list items is being referred to?

3

There are 3 answers

1
halbgut On BEST ANSWER

The HTML produced by this is invalid, due to multiple elements having the same id-attribute. The event-handler is simple to explain. the keys inside an event-map are split into event-type and optionally event-target. The event-target is passed to jQuery, which tolerates the invalid usage of ids. So jQuery adds event-listeners to all elements with the id player.

Matt K explained the rest pretty well.

1
Matt K On

In Meteor, you've got all the Templates that you've created. Each time you render a template, you give it a data context, which is a fancy way of saying it creates a new functional scope. That data context is stored in the templateInstance().data.

When you use #each, you create a new data context for whatever is inside. you can check this by putting a console.log(this) in your helper above the return.

In onCreated, onDestroyed, and onRendered, this points to the templateInstace(). So whenever you're in a callback, use this.data to access the data context.

In an events callback you have 2 args: event, template. Here, template points to the templateInstance() as well.

In a helper, this points to templateInstance().data so you just need to call this.

Spacebars isn't magic, it's just a way to make JS look like HTML. Assuming your templates are stored in views/templates.html, go look at .meteor/local/build/programs/web.browser/app/client/views/template.templates.html and it'll all make sense.

0
Kyll On

The {{#each}} template tag is special in that it changes its inner context.
In fact, each template can be used with a bag of data (for example the title and content of a post). This bag of data is registered inside the template's own context, and can be referred to with this.someData in helpers or by simply naming it in the template ({{someData}}).

Other template tags such as {{#if}} do not change the context of the template inside them. You could type inside a blog post template which receives a blog post object as data:

<h3>{{title}}</h3>
<p>{{content}}</p>

{{#if userIsSecretAgent}}
  <blink>{{secretData}}</blink>
{{/if}}

(The secret is that you had to blink at the same frequency as the text to be able to read it, that's why this tag was used for such a stupid long time)


{{#each}} allows you to change the inner context of the template to the current object of the iteration, and thus simply access the contents of the object you are iterating on.
Let's say you want to make a blog posts template which displays a list of blog posts:

{{#each blogPosts}}
  <!-- Here, how do you gain access to each title, each content, ..? -->
  <!-- Well, the context has changed! -->
  <!-- Now we can directly access each datum of the iterable through the context -->

  <!-- The result simply becomes: -->
  <h3>{{title}}</h3>
  <p>{{content}}</p>

  {{#if userIsSecretAgent}}
    <blink>{{secretData}}</blink>
  {{/if}}
{{/each}}

The context inside the {{#each}} block not being the same as the parent, but instead being a blog post, we can simply refer to each blog post fields by their names!

But now we're repeating code. Wouldn't it be great to reuse our previous template..?

{{#each blogPosts}}
  {{> blogPost}}
{{/each}}

The blogPost template needs a blog post object as its context, we are providing this context with the {{#each}} template tag.

Helpers work in the same way, except that you have to type this.title or this.content inside them. You also have to use Template.instance() to refer to the template in some places (rather than this).

See the SpaceBars docs for more sorcery.