0

I'm having problems understanding the event binding in a Backbone subview. My view is defined as follows:

TenantView = Backbone.View.extend({
  events: {
    "click": "setActive"
  },
  initialize: function() {
    this.parentEl = this.options.parentEl;
    return this.render();
  },
  template: new EJS({
    url: '/scripts/templates/tenant.ejs'
  }),
  render: function() {
    $(this.parentEl).children('ul').append(this.template.render(this.model));
    return this;
  },
  setActive: function(event) {
    return this.model.set('active', true);
  }
});

The template simply consists of an li containg an a. Done this way, clicks events on my view are not catched, my method setActive is never triggered.

When I extend my view by an el property like el: 'li' one of my (2) views acts properly and triggers the setActive function. The second view does not react at all. If I insepct the el property during the views initialization, the working view's el property points to the right li, the failing views el points to the first li that can be found in the page.

As one can see, I am totally lost when it comes to the meaning of the el property.

Question is, how can I bind a click on view to this very views setActive function?

Can anyone enlighten me please?

Regards Felix

GeorgieF
  • 2,687
  • 5
  • 29
  • 43

2 Answers2

2

First thing, you may read documentation and this. Explanation about el is given there.

As TenantView has parentEl property, I'm assuming it is being rendered from some parent_view. I would suggest some approach like below and give it a try.

var ChildView = Backbone.View.extend({
  tagName : "li", // change it according to your needs

  events : {
    "click": "setActive"
  },

  initialize : function() {
    _.bindAll(this, "setActive");
    // code to initialize child_view
  },

  render : function() {
    // as I'm not familiar with the way you are using template, 
    // I've put simple call to render template, but it should render the
    // content to be kept inside "li" tag
    this.$el.html(this.template()); 

    return this;
  },

  setActive : function(event) {
    // code to be executed in event callback
  }
});


var ParentView = Backbone.View.extend({
  el : "#parent_view_el",

  initialize : function() {
    // parent view initialization code
  },

  render : function() {
    // a place from where ChildView is initialized
    // might be a loop through collection to initialize more than one child views
    // passing individual model to the view

    var child_view = new ChildView();

    this.$("ul").append(child_view.render().$el); // equivalent to this.$el.find("ul")

    return this;
  }
});

Hope it helps !!

Community
  • 1
  • 1
Cyclone
  • 1,580
  • 1
  • 8
  • 14
  • Cyclone, thank you for your answer. In fact that's nearly what I am doing. The parentEl property points to the CollectionViews el property(which is a ul), so that I can render my ChildViews inside the ul. In my parent CollectionView I iterate over the collections models and create a ChildView instance for each. I have the feeling, that approach is totally wrong. – GeorgieF Nov 07 '12 at 13:11
  • Hmmm, thats what I've been following, works pretty well for me :) – Cyclone Nov 07 '12 at 13:55
  • Reading [this](http://stackoverflow.com/questions/5624929/backbone-view-el-confusion) actually solved my problem. See my comment below. Thanks again for the input. – GeorgieF Nov 07 '12 at 14:00
0

You have kind of disabled backbones natural behavior by introducing your parentEl property and circumventing the creation of a view element. You basically never wire up the views `elz property to anything.

If I get your code right, the TenantView is a single list item in a list of tenants.

In your TenantView you can do this to leverage backbones built in events:

render: function() {
    this.setElement(this.template.render(this.model));
    $(this.parentEl).children('ul').append(this.$el);
    return this;
}

The setElement function will use the return value of you template function to wire up the views element and also setup the events for you. In the documentation it is suggested to use setElement in favor of just assigning something to the el property. There is also the nifty $el property which will contain a cached jQuery (or Zepto) object of your view's element.

To go more in the backbone way personally I would consider something like this:

var TenantView = Backbone.View.extend({
    // =========
    // = Setup =
    // =========

    events: {
        "click": "setActive"
    },

    template: new EJS({
        url: '/scripts/templates/tenant.ejs'
    }),

    // =============
    // = Lifecycle =
    // =============

    initialize: function(model, options) {
        this.render();
    },

    render: function() {
        var content = this.template.render(this.model);
        this.$el.html(content);
        return this;
    },

    // ==========
    // = Events =
    // ==========

    setActive: function(event) {
        return this.model.set('active', true);
    }

});


var TenantList = Backbone.View.extend({

    // =========
    // = Setup =
    // =========

    id: "tenantList",
    tagName: "ul",

    // =============
    // = Lifecycle =
    // =============

    initialize: function() {
        this.render();
    },

    render: function() {
        if (this.collection.length > 0) {
            this.renderChildren();
        }
    },

    renderChildren: function() {
        var that = this;

        this.collection.each(function(tenant) {
            that.$el.append(new TenantView(tenant).$el);
        });
    }
});
Torsten Walter
  • 5,614
  • 23
  • 26
  • Torsten, thanks for your reply. setElement seems to properly set the el property of my views during rendering now. Unfortunately the click events are not catched either way. I think my whole approach of rendering ChildViews inside a CollectionView is totally wrong. – GeorgieF Nov 07 '12 at 13:18
  • Well in a kind of way... Backbone kind of expects you to have an element after initialization and only fill this with the render function. In theory the `setElement` function should take care of events. Which version of backbone do you use? – Torsten Walter Nov 07 '12 at 13:27
  • I'm using 0.9.2. What solved the problem so far was setting `tagname` to 'li' for the ChildView. Then remove the li from the template. I changed the render method of my ChildView to: this.el.innerHTML = this.template.render(this.model) and changed the render method of my CollectionView to: $(this.el).children('ul').append(viewInstance.render().el) where viewInstance's are iterated over. I'm still unsure whether this approach is the right one. – GeorgieF Nov 07 '12 at 13:35