Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backbone and bindAll: "func is undefined"

I've got problems using bindAll. The error I get is func is undefined. Any thoughts on what I'm doing wrong?

I've tried both

  • bindAll (fails with the above error) and
  • individual binds (don't work)
window.test = Backbone.View.extend({

  collection: null

  initialize: ->
    console.log('initialize()')
    console.log(this)
    # _.bindAll(this, ["render", "foo"])
    _.bind(this.render, this) # these don't throw errors, but binding won't work
    _.bind(this.foo, this) 
    @collection = new Backbone.Collection()
    @collection.bind "add",     @render
    @collection.bind "add",     @foo
    @render()

  foo: ->
    # won't be bound to the view when called from the collection
    console.log("foo()")
    console.log(this)
    console.log(this.collection) # undefined (since this is the collection, not the view)

  render: ->
    console.log("render()")
    console.log(this)
    return this

})
testInstance = new window.test();
# using _.bind instead of bindAll we get past this point, however this won't be the view
testInstance.collection.add({})
like image 727
sandstrom Avatar asked Nov 30 '22 08:11

sandstrom


2 Answers

You are using CoffeeScript, you don't need underscore's bind. CoffeeScript has it built in. Just use "fat arrows"

foo: =>
    #Your foo implementation goes here
render: =>
    # your render implementation goes here

Search for "fat arrow" here: http://jashkenas.github.com/coffee-script/

However, with regards to _.bindAll, you don't need to provide the method names as an array. You can do either _.bindAll(this) or _.bindAll(this, 'render', 'foo') (method names are var args, not an explicit list). See if that helps.

like image 153
Peter Lyons Avatar answered Dec 05 '22 14:12

Peter Lyons


Peter Lyons is correct on both counts. You'd want to pass each function as an argument to bindAll rather than passing an array of functions. And when using coffeescript the fat arrow is an awesome way to bind the function to the context in which it was defined.

I wanted to answer why _.bind wasn't working for you (because it took me a good while to figure it out). The answer is that _.bind doesn't alter the function you pass, it creates a new function with the arguments provided. It'd be more appropriately labeled createBoundFunction. So getting _.bind to work in your example would just be:

this.render = _.bind(this.render, this)
this.foo = _.bind(this.foo, this) 

Additionally, when stepping through the source I learned a lot about how functions are bound so I hope you don't mind a digression on function binding, starting with what coffee script does.

var __bind = function(fn, me){ return function(){return fn.apply(me, arguments); }; }

CoffeeScript inserts the above function into each file that uses the fat arrow (=>). This is the old way. It creates and returns a new function that calls your function and applies the context and arguments that you pass. CoffeeScript then generates the constructor and calls __bind for each fat arrow defined function. For Peter Lyon's solution the generated code would like this:

this.render = __bind(this.render, this)
this.foo = __bind(this.foo, this) 

In my current project I have 9 views that use the fat arrow so I have __bind defined 9 times, which seemingly violates DRY but who cares, its generated for me.

ECMAScript5 proscribes a new method on the Function prototype.

 Function.prototype.bind(thisArg [, arg1[, arg2[, ...]]])

This method would reduce your code to looking like this:

this.render = this.render.bind(this)
this.foo = this.foo.bind(this)

with no outside library or generated method required. The catch is that this is only supported in the latest+greatest browsers(FF4, IE9, CH10>), so using it exclusively won't be possible for a few years.

Underscore combines these two concepts:

_.bind = function(func, obj) {
  if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func,   slice.call(arguments, 1));
  var args = slice.call(arguments, 2);
  return function() {
    return func.apply(obj, args.concat(slice.call(arguments)));
  };
};

Where nativeBind is equal to Function.prototype.bind. So underscore checks for the availability of the new ECMA5 bind method and if this doesn't exist creates an anonymous function that calls your function with the context of your choice.

If I knew of or could find information on some advantage that Function.prototype.bind confers I'd say to avoid the CS fat arrow and use _.bind, especially for backbone projects where you have underscore already included in your libraries, but I don't know that it confers advantage so it probably doesn't matter one way or the other.

like image 39
c3rin Avatar answered Dec 05 '22 14:12

c3rin