Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Click is calling function twice in backbone

I have created very quick example of alert triggering twice on backbone View.

http://jsfiddle.net/feronovak/8svqX/

This is does nothing special, it really is for me to understand how to call separate Views correctly, so they dont trigger previous click method. Right now I generate two Views, second View overwrites form in #boxSearch. When I click button Search it also generates alert doSearch SearchLocation. I was expecting to see only alert doSearch SearchLatitude. What I have done wrong?

var Geo = {
    Views: {},
    Templates: {}
};

Geo.Templates.SearchLocation = _.template( $("#tpl-search-location").html());
Geo.Templates.SearchLatitude = _.template( $("#tpl-search-latitude").html());

Geo.Views.SearchLocation = Backbone.View.extend({
    initialize: function() {
        this.render();
    },
    el: $("#boxSearch"),
    template: Geo.Templates.SearchLocation,
    render: function() 
    {
        $(this.el).html(this.template);
    },
    events: {
        "click input[type=button]": "doSearch"
    },
    doSearch: function(e) {
        e.preventDefault();
        alert('doSearch SearchLocation');
    }
});

Geo.Views.SearchLatitude = Backbone.View.extend({
    initialize: function() {
        this.render();
    },
    el: $("#boxSearch"),
    template: Geo.Templates.SearchLatitude,
    render: function() 
    {
        $(this.el).html(this.template);
    },
    events: {
        "click input[type=button]": "doSearch"
    },
    doSearch: function(e) {
        e.preventDefault();
        alert('doSearch SearchLatitude');
    }
});

$(document).ready(function(e) {
    var searchLocation = new Geo.Views.SearchLocation();
    var searchLatitude = new Geo.Views.SearchLatitude();
});
like image 702
feronovak Avatar asked Mar 25 '13 23:03

feronovak


2 Answers

This is the "ghost view" problem often seen with Backbone apps:

  • You attach a view to a DOM element
  • You attach another view to the same DOM element
  • You trigger an event on the DOM element (or worse, trigger an event somewhere else that both views listen to)
  • Both views fire their event handlers, often wreaking havoc.

As @machineghost points out, there's nothing in your code to unbind the first view, so both views are attached to #boxSearch, and both views will respond to the click event. There are several ways around this:

  • You could call e.stopPropagation() (docs) in the event handlers (I think this is what you're trying to do with e.preventDefault()). This will prevent the event from bubbling up once it's triggered, so the last defined view would catch it. This works, but note that you still have the two views hanging around, and the outdated ghost view might have other side-effects.

  • You could have each view render its own DOM element, with a class .boxSearch, and then call view.remove() on the one you no longer want. This is probably the cleanest option, but it means creating most of your DOM on the fly, which is a bit more expensive and hard to manage than coding it in HTML.

  • You can give each view a cleanup method, e.g. .clear() or .exit(), which could call view.undelegateEvents() and view.stopListening(). Then make sure you call this method when you are done with a view.

If you're careful about making sure it gets called and making sure it clears everything that needs to be cleared, then it works well.

like image 193
nrabinowitz Avatar answered Oct 10 '22 20:10

nrabinowitz


When you add another view to an element, it doesn't automatically remove the old one; if you want to do that you have to explicitly call yourFirstView.remove(). However, doing that will remove the element as well; if you just want to unbind the events without removing the element you can use yourFirstView.undelegateEvents() instead.

It's worth mentioning that this is a deliberate behavior of Backbone: oftentimes it's helpful to be able to have 2+ views on a single el.

like image 22
machineghost Avatar answered Oct 10 '22 18:10

machineghost