Wednesday, June 25, 2014

Determine If Backbone View Has Rendered

I've encountered some tricky situations where I'm not sure if my Backbone view has made it's way into the DOM yet. In other words - how can I tell whether my view has been rendered or not? This situation can lead to some really subtle bugs. For example, some other JavaScript in your view expects the view element in the DOM. But when it's not there, the failure mode is anything but intuitive.

So I figured there has to be a straightforward approach to only doing doing something if the view element is in the DOM. The inherent view functionality gets us partway there by ensuring that there's always an element. I needed a way to check if the element was actually in the DOM, so I could reliably avoid running code that expects the element to be there when it's not.

I found this approach to be very helpful, and here's an example of how I fit it into a Backbone view.

var MyView = Backbone.View.extend({
    
    tagName: 'ul',
    
    initialize: function( options ) {
        var coll = options.collection;

        this.listenTo( coll, 'add', this.onAdd );
        this.listenTo( coll, 'remove', this.onRemove );
        
        coll.forEach( _.bind( function( m ) {
            $( '<li/>' )
                .attr( 'id', m.cid )
                .text( m.cid )
                .appendTo( this.$el );
        }, this ));
        
        this.$el.appendTo( 'body' );
    },
    
    onAdd: function( model, collection ) {
        if ( !this.rendered() ) {
            return;    
        }

        $( '<li/>' )
            .attr( 'id', model.cid )
            .text( model.cid )
            .insertAfter( this.$el.children().get(
                collection.indexOf( model ) - 1 ));
    },
    
    onRemove: function( model ) {
        if ( !this.rendered() ) {
            return;
        }
        
        this.$( '#' + model.cid ).remove();
    },
    
    rendered: function() {
        return $.contains( document, this.el );
    }

});

Here you can see that both the onAdd and onRemove event handlers, that respond to events in the collection, are setup before the view element is rendered into the DOM. However, the rendered() method tells us whether the element is in fact part of the DOM yet or not. If it isn't, there's no point in these event handlers doing any work.