Anonymoose
Anonymoose

Reputation: 2471

Call function on object in underscore template

I have a button in the HTML of my template. How can I make it call a function on the object that is rendered at that time?

I realize this is refering to the button at that moment. But how do I refer to the person?

FULL CODE: JS Fiddle

TEMPLATE

<script type="text/template" id="tpl-person">
    <tr>
        //...
        <td><button onclick="this.happyBirthday()">Birthday</button></td>
    </tr>
</script>

JAVASCRIPT

var Person = function(name, age){

    //....

    this.happyBirthday = function(){
        this.age ++;
    }

    this.render = function(){
        var template = _.template($("#tpl-person").html());
        $("#peopleWrapper").append(template(this));
    }

    //constructor
    this.name = name;
    this.age = age;
    this.render();
}

Upvotes: 0

Views: 3223

Answers (1)

Jon
Jon

Reputation: 437376

Why this won't work

It is not possible to do this because attaching the event handler from HTML requires that the handler function be serialized (i.e. in source form). There is no real way to serialize a function in JavaScript, and even if you could there is another showstopper: inside the function this to be a reference to an existing JavaScript object which is impossible because again, everything that you do needs to be serialized.

What can be done instead

An easy workaround would be to attach the event handler with jQuery at the time the template is rendered.

Taking into consideration the desired value of this, you can define your event handler function from within any Person method as

// $.proxy because otherwise "this" would be the clicked button element
$.proxy(this.happyBirthday, this);

Attaching the click handler is straightforward:

var clickHandler = $.proxy(this.happyBirthday, this);
var html = $(template(this));
$("#peopleWrapper").append(html.find("button").click(clickHandler).end());

A better approach

That said, this does not look like a good way to arrange things. Consider a different suggestion: attach the Person object to the rendered template (e.g. through jQuery's .data) and refer to it from within the click handler; the handler itself can be delegated to save on functions and allow dynamically adding more rendered templates.

For example:

The HTML template itself does not attach any handlers at all.

When adding the rendered template use .data to associate it with the person object and do something to mark it as person-associated visible from a DOM perspective. This could be as simple as adding a data-person attribute (or a CSS class):

$("#peopleWrapper").append(
  $(template(this)).data("Person", this).attr("data-person", ""));

Attach a delegated handler to the DOM that recognizes clicks on the buttons and, starting from the clicked element, finds the associated person object and calls the happyBirthday method:

$("#peopleWrapper").on("click", "[data-person] button", function() {
    $(this).closest("[data-person]").data("Person").happyBirthday();
});

See it in action.

Upvotes: 2

Related Questions