Green Fireman Green Fireman - 6 months ago 16
Javascript Question

Backbone - Get a certain model from a collection that is iterated in a template

Let's say my template looks like this:

<script type="text/template" id="template">
{{#each []}}
<div>{{this.id}}</div>
<div>{{this.name}}</div>
<div>{{this.description}}</div>
<input id="my-btn" type="button">Button</input>
{{/each}}
</script>


The model and the collection:

var Item = Backbone.Model.extend({
defaults: {
id: null,
name: '',
description: ''
}
});

var ItemCollection = Backbone.Collection.extend({
model: Item,
url: function() {
return '/items';
}
});


And in the view I simply fetch the items and display them:

var ItemsView = Backbone.View.extend({
el: $('#items'),
template: Handlebars.compile($('#template').html()),
items: new ItemCollection(),
initialize: function() {
var self = this;
this.items.fetch({success: function() {
self.render();
}});
},
render: function() {
var items = new ItemCollection();
var html = this.template(items.toJSON());
this.$el.html(html);
}
});


Every item with it's fields in the template has it's own button.

Right now I need to add a functionality that will trigger when the button in the template is pressed, so on click some function has to get the item, that is associated with the button in the template.

How can I do that?

Answer

Here is one way to accomplish what you are asking for -- simply add a click handler to the events hash (http://backbonejs.org/#View-events). I added data-dash attributes to the buttons so that in the click handler you can determine which button was clicked / model to act upon.

var ItemView = window.Backbone.View.extend({
    tagName: 'div',
    template: Handlebars.compile($('#template').html()),
    events: {
            'click .item-btn': 'clickHandler'
    },
    render: function() {
      this.$el.html(this.template({
        items: collection.toJSON()
      }));
      return this; // backbone convention -- return the view
    },
    clickHandler: function(e) {
           alert($(e.target).data().itemId + ' clicked');
    }
  });

The above view assumes the following template:

<script type="text/template" id="template">
      {{#each items}}
      <div>{{this.id}}</div>
      <div>{{this.name}}</div>
      <div>{{this.description}}</div>
      <input class="item-btn" data-item-id="{{this.id}}" type="button" value="Button {{this.id}}"></input>
      {{/each}}
    </script>

Here is a working demo: https://jsfiddle.net/9cgzcc3y/

Another way to accomplish this (and one that I prefer personally) is creating a single view for each model. This way, each view will be backed by an individual model.

var ItemView = window.Backbone.View.extend({
    tagName: 'div',
    template: Handlebars.compile($('#template').html()),
    events: {
           'click .item-btn': 'clickHandler'
    },
    render: function() {
        var that = this;
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    },
    clickHandler: function() {
        alert('clicked: ' + this.model.get('id'));
    }
  });

  var ItemsView = window.Backbone.View.extend({
    tagName: 'div',
    initialize: function() {
      this.collection = collection;
    },
    render: function() {
        var frag = document.createDocumentFragment();
        this.collection.each(function(model) {
          frag.appendChild(new ItemView({model: model}).render().el);
        });
      this.$el.append(frag);
      return this;
    }
  });

  $('#items').append(new ItemsView().render().$el);

Here is a working demo of this alternative: https://jsfiddle.net/9cn3swg2/