Luca Reghellin
Luca Reghellin

Reputation: 8101

Backbonejs: is passing a parent's element reference to a child view a good practice?

giving a parent and a child view, I'd like 2 things:

In practice, from the parent view, instead of this:

,add_bannerbox_view:function(model, collection, options){
    var bannerbox = new BannerBoxView({ model: model });
    this.bannerbox_views[model.cid] = bannerbox;
    this.bannerbox_container.append(bannerbox.el);
    bannerbox.render();
}

I'd like simply this;

    ,add_bannerbox_view:function(model, collection, options){
        //here, BannerBoxView is supposed to render itself from initialize() 
        this.bannerbox_views[model.cid] = new BannerBoxView({ model: model, parent:this.el });
    }

But I was wondering: is passing a parent's elem to the child a good practice? Or does it have some bad drawback?

Upvotes: 3

Views: 999

Answers (2)

Luca Reghellin
Luca Reghellin

Reputation: 8101

I partially answer to myself. More than circular references (I'm passing only a dom element), drawbacks could arise for the self-appending functionality I'd like to use in child's render() method. The reason is possible memory leaks when having large number of views. There is a good explanation here:

http://ozkatz.github.io/avoiding-common-backbonejs-pitfalls.html

I should use var container = document.createDocumentFragment() in the parent view and then maybe pass container to the child view.

Also, following discussions above, and still not fully convinced of the various points (mine first :P) I'm using sort of bridge code. For now, I like doing this: I don't pass parent's dom element as a constructor argument. Instead, I pass it directly to the child's render(). The code is cleaned out to the bare bones:

//parent
var CustomBannersView = Backbone.View.extend({

initialize:function(){
    this.groups_container = $('.groups-container');
    this.group_views = {};
    this.init();
    this.set_events();
}

,init:function(){
    //instantiate views without rendering for later use
    this.collection.each(function(model){
        this.group_views[model.cid] = new GroupView({ model:model, id:'group-' + model.cid });
    },this);
}

,render:function(){
    var temp_box = document.createDocumentFragment();

    //render views without dom refresh. Passing the box.
    _.each(this.group_views, function(groupview){ groupview.render(temp_box); });

    //add container
    this.groups_container.append(temp_box);
}

//dom events ----
,events:{
    'click .create-gcontainer-button': function(){
        this.collection.add(new Group());
    }
}

,set_events:function(){
    this.listenTo(this.collection,'add',function(model, collection, options){           
        //render a single subview, passing the main container
        //no refresh problem here since it's a single view
        this.group_views[model.cid] = new GroupView({ model: model, id:'group-' + model.cid }).render(this.groups_container);
    });
}
});//end view


//child
var GroupView = Backbone.View.extend({
  tagName: 'fieldset'
  ,className: 'group'

  ,initialize:function(){
    this.template = Handlebars.compile($('#group-container').html()); 
  }

  ,render:function(box){//box passed by parent
    this.$el.html(this.template(this.model.toJSON()));
    $(box).append(this.$el);
    //now I can set things based on dom parent, if needed
    return this;
  }
});

Upvotes: 0

anushr
anushr

Reputation: 3400

Loose coupling is almost always preferable to tight coupling. The two best reasons I can think of are:

  • Reusable. Can be used by anywhere in your app without worrying about dependencies.
  • Testable. Can be tested independent of other components.

By requiring the child view to have a reference to the parent view, you are promoting tight coupling i.e. the child view becomes dependent on the parent view. This makes reusability extremely difficult, and if you're writing unit tests, you're going to have to instantiate or mock a parent class just so you can test the child. This is unnecessary and tedious.

If really what you're trying to do is have the child view automatically render, just extend the core Backbone.View and include a helper function that your parent views can call.

var MyView = Backbone.View.extend({
    renderChild: function(view, options) {
        var childView = new view(options);
        this.views[options.model.cid] = childView;
        this.$el.append(childView.el);
        childView.render();
    }
});

Then, you can define your parent views like so:

var ParentView = MyView.extend({
    add_bannerbox_view: function() {
        this.renderChild(BannerBoxView, {model: model});
    }
});

The helper function we made will let you instantiate, append and render your child views with a single line of code.

Upvotes: 2

Related Questions