Phil Giles

backbone child view rendering pt. 2

My last blog post showed how I fixed an issue with child view rendering in backbone. The reason I needed to come up with a new solution to rendering child views was due to the fact I needed to know element heights within one view and pass them to another.

This solution worked great…for a while! Another issue arose soon after which meant a change had to be made again!

the new issue

For the last blog post, I simplified the code a lot and didn’t quite represent what I was doing fully. But to explain this new issue I need to add some more detail. In my last example, we were adding two child views to a page, and one was sending its height to the other. This is still true, but rather than adding these two views to the page they are wrapped in another view which is then added to the page.

Why I hear you ask? Well, the wrapper view is a component that needs to be reused multiple times in the same page, and this is exactly where my new issue came in. Due to the multiple instances of this component, and the amount of data and rendering each one does, it made loading the page almost impossible for large data sets. This is due to the fact that I was passing the holder div al the way down the view chain and rendering as I go, which turned out to be very heavy.

the new solution

So I ended up going back to the drawing board with the rendering issue. I made a change to the code to make it all work again in the original setup:

var PageView = Backbone.View.extend({
  initialize : function (options) {
    this.holder = $('.container');
  },
  render : function(){
    this.$el.html(this.template());

    let componentViews = [];

    this.model.data.forEach(function(data){
      componentViews.push(new ComponentWrapperView({model: data}));
    });
    
    componentViews.forEach(function(view){
      this.$el.append(view.render().el);
    });
    
    this.holder.append(this.$el);
  }
});

var ComponentWrapperView = Backbone.View.extend({
  initialize : function (options) {
    this.sidebarView = new sidebarView({});
    this.tableView = new tableView({'sidebar': this.sidebarView});
  },
  render : function(){
    this.$el.html(this.template());
    this.$el.append(this.sidebarView.render().el);
    this.$el.append(this.tableView.render().el);
  }
});

var tableView = Backbone.View.extend({
  initialize : function (options) {
    this.sidebar = options.sidebar;
  },
  render : function(){
    this.$el.html(this.template(this.model.toJSON()));
    this.sidebar.setHeights(this.el.outerHeight());
  }
});

var sidebarView = Backbone.View.extend({
  render: function(){
    this.$el.html(this.template(this.model.toJSON()));
  },
  setHeights: function(height){
    this.el.outerHeight() = height;
  }
});

This worked fine and everything rendered much faster and smoother. But we were still missing the sending of heights between views.

After trying a few different methods, I still couldn’t get the heights to send across from the table view to the sidebar view. But eventually, I came up with a solution! This basically involved creating an afterRender() method which is called once the DOM elements are added. Here’s what I did:

var PageView = Backbone.View.extend({
  initialize : function (options) {
    this.holder = $('.container');
  },
  render : function(){
    this.$el.html(this.template());

    let componentViews = [];

    this.model.data.forEach(function(data){
      componentViews.push(new ComponentWrapperView({model: data}));
    });
    
    componentViews.forEach(function(view){
      this.$el.append(view.render().el);
      view.afterRender();
    });
    
    this.holder.append(this.$el);
  }
});

var ComponentWrapperView = Backbone.View.extend({
  initialize : function (options) {
    this.sidebarView = new sidebarView({});
    this.tableView = new tableView({'sidebar': this.sidebarView});
  },
  render : function(){
    this.$el.html(this.template());
    this.$el.append(this.sidebarView.render().el);
    this.$el.append(this.tableView.render().el);
  }
  afterRender : function(){
    this.tableView.sendHeights();
  }
});

var tableView = Backbone.View.extend({
  initialize : function (options) {
    this.sidebar = options.sidebar;
  },
  render : function(){
    this.$el.html(this.template(this.model.toJSON()));
  },
  sendHeights : function(){
    this.sidebar.setHeights(this.el.outerHeight());
  }
});

var sidebarView = Backbone.View.extend({
  render: function(){
    this.$el.html(this.template(this.model.toJSON()));
  },
  setHeights: function(height){
    this.el.outerHeight() = height;
  }
});

And it worked! This is due to the fact that the DOM is ready by the time the afterRender() is called, resulting in the element heights being available.

thats all folks

So that’s an update on my backbone view rendering problem. I feel like I have cracked the issue this time, but if you think you have a better way to do it, let us know in the comments.

If you enjoyed the read, drop us a comment below or share the article, follow us on Twitter or subscribe to our #MetaBeers newsletter. Before you go, grab a PDF of the article, and let us know if it’s time we worked together.

blog comments powered by Disqus