How To Maintain Scope Of Current View Within Backbone After Animations Or Timeouts

Backbone.js tends to switch the scope of "this" view after things like timeouts, intervals, AJAX requests, and jQuery animations. Here's how to retain scope.

jquery,backbone,underscore,scope

06/18/2014

This is a simple tip that Matt Kenefick showed me to solve issues within a framework on delayed events such as animations or settimeouts. The issue encountered is this - you have your framework setup such that "this" refers to the view you are working in:


;(function(namespace) {
    'use strict';

    namespace.View_Module = namespace.View_Base.extend({
      this.doAnimation();

      doAnimation: function(){
        console.log(this.doAnimation); //refers to the function within View_Module_Collab
      }

    });

})

And now let's say you want to delay the doAnimation() function a half a second:


;(function(namespace) {
    'use strict';

    namespace.View_Module = namespace.View_Base.extend({
      setTimeout(function(){
        this.doAnimation();
      },500)

      doAnimation: function(){
        console.log(this.doAnimation); //undefined
        console.log(this); //refers to the settimeout itself
      }

    })
})

Here, we've lost scope of the view (View_Module). So how to preserve that reference? Use underscore.js's "bind" method:


;(function(namespace) {
    'use strict';

    namespace.View_Module = namespace.View_Base.extend({
      setTimeout(_.bind(this.doAnimation,this,500),

      doAnimation: function(){
        console.log(this.doAnimation); //logs the function properly
        console.log(this); //refers to the View_Module
      }

    })
})

How about parameters? Let's look at a jQuery animation:


;(function(namespace) {
    'use strict';

    namespace.View_Module = namespace.View_Base.extend({
      setTimeout(_.bind(this.doAnimation,this,500),

      doAnimation: function(){
              var param = 'complete';
              $('.element').animate({'left': '100%' },{
                duration: 500,
                complete: _.bind(this.onAnimationComplete,this,param),
              });
      },

      onAnimationComplete:function(param){
              console.log('this'); //still refers to our view
              console.log(param); //logs 'complete'
      }


    })
})

Super useful tip for a somewhat confusing but common scenario.