6

I need to bind two events in my backbone.js-view in order to toggle a menu. The idea is that if a button with the id #toggler is clicked the menu appears and any click outside the #menu element will hide the menu.

Unfortunately I cannot get this working with backbone's event binding without having the outsideMenuHandler() called on every click regardless if I clicked on the #menu element, or not.

What should I change to make this work?

This is what I have done in backbone.js, which doesn't work as intended:

myView = Backbone.View.extend({

    events: {
        'click #menu': 'insideMenuHandler',
        'click':       'outsideMenuHandler'
    }

    insideMenuHandler: function(e) {}, // Called when clicking #menu
    outsideMenuHandler: function(e) {} // Called on every click both on and off #menu

}
 

Just as a reference, here's what I would do using jQuery alone:

$(document).click(function(event) {
    if ( $(event.target).closest('#menu').get(0) == null ) {
        $("#menu").slideUp();
    }
});   
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Industrial
  • 41,400
  • 69
  • 194
  • 289

2 Answers2

14

There are a couple things you need to sort out.

First of all, if your insideMenuHandler returns false or calls e.stopPropogation() then your outsideMenuHandler won't get called for clicks on #menu. For example:

http://jsfiddle.net/ambiguous/8GjdS/

But that's not your whole problem. Your outsideMenuHandler will only be called for clicks on your view; so, if someone clicks on the page outside your view, your outsideMenuHandler won't get called and your menu won't go down. If you want the menu to go down when someone clicks anywhere outside #menu, then you'll have to manually bind to body and manually unbind when your view is destroyed. Something like this:

var myView = Backbone.View.extend({
    events: {
        'click #menu': 'insideMenuHandler'
    },

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

    render: function() {
        // Both <body> and <html> for paranoia.
        $('body, html').on('click', this.outsideMenuHandler);
        // ...
        return this;
    },

    remove: function() {
        // Clean up after ourselves.
        $('body, html').off('click', this.outsideMenuHandler);
        // ...
    },

    // ...
    outsideMenuHandler: function(e) {
        // ...
        return false;
    }
});

and then be sure to properly remove your view. For example:

http://jsfiddle.net/ambiguous/MgkWG/1/

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Thanks a lot for your answer. Very appreciated! A quick question - as I understand, letting a click event return `false` will prevent any "default event" from occurring, meaning that all other clicks will be disabled as the `outsideMenuHandler` returns `false`. Am I getting myself into trouble if I let my `outsideMenuHandler` return `true` after performing my logic (hiding my menu)? – Industrial Jan 31 '12 at 10:59
  • 1
    @Industrial: You'll end up with double calls to `outsideMenuHandler` from the `body, html` binding but that might not be a problem. You could probably get away with only binding `outsideMenuHandler` to `html` but you'd want to browser check that to make sure it behaves properly in all the browsers you care about. – mu is too short Jan 31 '12 at 19:01
-1

The problem is, that you bind your event to the DOM element that you define as the views el. So if this is not the window.document as in your jquery example you cant notice any click outside them. The easiest way in your example would be to add the events manually with jquery and not with backbone.

Andreas Köberle
  • 106,652
  • 57
  • 273
  • 297