The context of "this" in Meteor template event handlers (using Handlebars for templating)
Asked Answered
B

3

42

A quick question on the context of the event handlers for templates in Meteor (with Handlebars).

  • In the section of Documentation on template instances (http://docs.meteor.com/#template_inst) it is mentioned that "Template instance objects are found as the value of this in the created, rendered, and destroyed template callbacks and as an argument to event handlers"
  • In the Templates section (http://docs.meteor.com/#templates) it says "Finally, you can use an events declaration on a template function to set up a table of event handlers. The format is documented at Event Maps. The this argument to the event handler will be the data context of the element that triggered the event."

Well, this is only partially true. Let's use an example from the docs:

<template name="scores">
  {{#each player}}
    {{> playerScore}}
  {{/each}}
</template>

<template name="playerScore">
  <div>{{name}}: {{score}}
    <span class="givePoints">Give points</span>
  </div>
</template
Template.playerScore.events({
  'click .givePoints': function () {
    Users.update({_id: this._id}, {$inc: {score: 2}});
  });

Here the "this" context of the 'click .givePoints' event handler is indeed the template instance of playerScore. Let's modify the html:

<template name="scores">
  <span class="click-me">Y U NO click me?<span>
  {{#each player}}
    {{> playerScore}}
  {{/each}}
</template>

<template name="playerScore">
  <div>{{name}}: {{score}}
    <span class="givePoints">Give points</span>
  </div>
</template>

... and add an event handler for .click-me on the scores template:

Template.scores.events({
  'click .click-me': function () {
    console.log(this);
  }
});

Now, if you click the span, what do you get logged? The Window object! What did I expect to get? The template object! Or maybe the data context, but it's neither. However, inside the callbacks (e.g. Template.scores.rendered = function(){ ... }) the context of "this" is always the template instance.

I guess my real question would be: is this something to do with

  • a bug in Handlebars, Meteor or somewhere in between?
  • slightly incomplete documentation on the templates?
  • me completely misinterpreting the docs or not understanding something fundamental about Meteor or Handlebars?

Thanks!

Berfield answered 28/2, 2013 at 13:50 Comment(3)
That is a right question. I've always accessed data in the events using function(event,template), and then through template.data.Treble
Wish I could upvote it twiceKweilin
@ТаняТ. you can mark the question as favorite, like I did ;-)Streptomycin
A
27

This video explains the concepts:

http://www.eventedmind.com/posts/meteor-spark-data-annotation-and-data-contexts.

The direct answer to your question:

The thisArg inside an event handler should point to a data context. But sometimes the data context is undefined. When you use the Function.prototype.call(thisArg, ...) in JavaScript, if the thisArg is undefined (e.g. a dataContext is undefined) the browser will set this equal to window. So, the docs aren't wrong per se but the event handling code isn't guarding against the possibility of a data context being undefined. I'm guessing that will be fixed in short order.

So, what produces a data context for a template? Normally your root template won't even have a data context. In other words, the Template function is called without an object. But if you use the {{#with block helper or the {{#each iterator, a data context will be created for each item in the list, or in the case of the with helper, the object.

Example:

var context = {};

<template name="withHelper">
  {{#with context}}
    // data context is the context object
  {{/with}}
</template>

var list = [ {name: "one"}, {name: "two"} ];

<template name="list">
  {{#each list}}
    {{ > listItem }} // data context set to the list item object
  {{/each}}
</template>
Animate answered 1/3, 2013 at 2:50 Comment(5)
The data context getting set to window if the data context is undefined has been fixed in the latest version of Meteor. So, now this inside an event handler always points to an object, either the data context or an empty object if there is no data context.Animate
Thanks for bringing it up :-).Animate
I just updated to 0.5.9 and I still get Window if the data context isn't set... Am I missing anything?Berfield
UPDATE: yes, I am. "this" inside event handlers is now always an object (empty one if there's no data context). However, it's still Window inside helpers unless there's data context set...Berfield
Ah this got missed. I'll open a Github issue.Animate
H
13

The first parameter in the function is the event. So you could use the target of the event to grab your element.

Template.scores.events({
  'click .click-me': function (event, template) {
    console.log(event.target);
    $(event.target).text("O but I did!");
  }
});
Heterodox answered 28/2, 2013 at 16:23 Comment(3)
Thanks Brandon. This is indeed one way to grab the element (although I would generally use currentTarget). However it doesn't answer my question: bug, docs or me?Berfield
I would say docs. Esp since meteor is still pre release. Docs are probably the hardest thing to keep up to date and to be fully inclusive. In situations like these, I just start adding params to the function and then console logging them. eg function (param1, param2, param3){ console.log(param1); console.log(param2); console.log(param3);}Heterodox
You can get them all at once with console.log(arguments).Lyndseylyndsie
C
0

A curious thing here I just discovered:

Here I have my template context in 'this':

Template.myTemplate.events({
    "click .my-select": function(e,t){
        console.log(this)
    }
});

But this variation (arrow) will put the Window object in 'this':

Template.myTemplate.events({
    "click .my-select": (e,t) => {
        console.log(this)
    }
});
Capitation answered 17/5, 2023 at 15:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.