25

I can attach handlers to Backbone Views like:

var TodoView = Backbone.View.extend({
    events: {
        "xxx": "eventHandler1"
        "yyy": "eventHandler2"
    }
});

But what if I want to attach more than 1 handler to the same event?

var TodoView = Backbone.View.extend({
    events: {
        "xxx": "eventHandler1"
        "yyy": "eventHandler2"
        "xxx": "eventHandler3" // this isn't valid ... at least in CoffeeScript
    }
});

I could create a custom handler like

function compositeXXX() { eventHandler1(); eventHandler2 }

But this doesn't seem ideal ...

mu is too short
  • 426,620
  • 70
  • 833
  • 800
jm2
  • 763
  • 2
  • 13
  • 27

2 Answers2

36

This:

events: {
    "xxx": "eventHandler1",
    "yyy": "eventHandler2",
    "xxx": "eventHandler3"
}

won't work because events is an object literal and you can have at most one (key,value) pair in an object. That would probably be the same as saying:

events: {
    "xxx": "eventHandler3",
    "yyy": "eventHandler2"
}

This CoffeeScript:

events:
    "xxx": "eventHandler1"
    "yyy": "eventHandler2"
    "xxx": "eventHandler3"

is functionally identical to the JavaScript version and won't work for the same reason.

Andy Ray's idea of using

'event selector': 'callback1 callback2'`

won't work either as Backbone won't understand that it should split the value on whitespace; similarly, this:

'event selector': [ 'callback1', 'callback2' ]

won't work because Backbone doesn't know what to do with an array in this context.

Views bind their events through delegateEvents and that looks like this:

delegateEvents: function(events) {
  // Some preamble that doesn't concern us here...
  for (var key in events) {
    var method = events[key];
    if (!_.isFunction(method)) method = this[events[key]];
    if (!method) throw new Error('Method "' + events[key] + '" does not exist');
    // And some binding details that are of no concern either...
  }
}

So method starts out as the value for 'event selector'. If it is a function from something like:

'event selector': function() { ... }

then it is used as-is, otherwise it is converted to a property of this:

method = this[events[key]]; // i.e. method = this[method]

If one were bold, one could adjust delegateEvents to understand an array or whitespace delimited string:

// Untested code.
var methods = [ ];
if (_.isArray(method))
  methods = method;
else if (_.isFunction(method))
  methods = [ method ];
else
  methods = method.split(/\s+/);
for (var i = 0; i < methods.length; ++i) {
  method = methods[i];
  if (!_.isFunction(method))
    method = this[method];
  // And the rest of the binding stuff as it is now with a possible adjustment
  // to the "method does not exist" exception message...
}

A fairly simple patch like that would allow you to use a whitespace delimited list of handlers:

'event selector': 'callback1 callback2'

or an array of handlers:

'event selector': [ 'callback1', 'callback2' ]

or even a mixed array of method names and functions:

'event selector': [ 'callback_name1', function() { ... }, 'callback_name2' ]

If you don't want to patch your Backbone or forward such a patch to the Backbone maintainers then you could go with your original "manual dispatching" idea:

'event selector': 'dispatcher'
//...
dispatcher: function(ev) {
    this.handler1(ev);
    this.handler2(ev);
}
mu is too short
  • 426,620
  • 70
  • 833
  • 800
9

I solved this issue by using jQuery's event namespaces

var TodoView = Backbone.View.extend({
    events: {
        "xxx.handler1": "eventHandler1",
        "yyy": "eventHandler2",
        "xxx.handler3": "eventHandler3"
    }
});

This isn't what event namespaces were originally intended for, but as long as they don't clash with other namespaces it shouldn't cause a problem.

The main issue is just that you can only have one value per key in an object and this makes the keys unique.

Jiaaro
  • 74,485
  • 42
  • 169
  • 190