Search code examples
javascriptbackbone.jsunderscore.js

passing an array of names to _.bindAll - Arguments list has wrong type


We use a common pattern in Backbone Views. We have event objects that look like so:

var TokenInputBaseView = Backbone.View.extend({

  events: {
    'click .remove-token' : 'click_removeToken',
    'mousedown .add-token': 'click_addToken',
    'keydown input'       : 'keydown_input',
    'click input'         : 'click_input',
    'focus .token-input'  : 'focus_input',
    'blur .token-input'   : 'blur_input',
  },

In nearly every case we want all the event handlers bound to the View, rather than to their event object. So we do this:

initialize: function(){
  _.bindAll(this, 'click_removeToken', ...);
}

We have to manually list every event name. It would be ideal if we could simply pass in an array, but underscore does not enable this use:

_.bindAll(this, _.values(this.events));

Underscore wants the individual names to be passed in as arguments rather than an array. However, this also does not work:

_.bindAll.apply(this, _.values(this.events).unshift(this));

Javascript gives this error:

"Uncaught TypeError: Function.prototype.apply: Arguments list has wrong type"

Any ideas on a good way to simplify the use of bindAll so that it does not require manually listing all the function names to bind?


Solution

  • You are passing bindAll the return of unshift, which is the length of the modified array. You need to either store an array reference, modify it, and then pass that reference to apply or employ some other trickery:

    // note, no need to bind to `this`
    _.bindAll.apply(null, [this].concat(_.values(this.events)));
    

    A short example:

    var target = document.querySelector('#target');
    
    var map = {
      'foo': 'foo',
      'bar': 'bar'
    };
    
    function C() {
      this.value = 'Value';
      _.bindAll.apply(null, [this].concat(_.values(map)));
    }
    
    C.prototype.foo = function() {
      console.log('hello', this);
      target.textContent = this.value + ' set by foo';
    }
    
    C.prototype.bar = function() {
      return this.value;
    }
    
    var c = new C();
    
    document.addEventListener('click', c.foo);
    <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
    
    <div id="target">Click me</div>