Eure Backbone.js Events triggern alle gleichzeitig und ihr wisst nicht, warum? Habt ihr eine Collection von Models, die gerendert werden soll und sieht euer Code ungefähr so aus?

Markup

1    <html>
2            <head> ... </head>
3            <body>
4                    <div id="id">
5                            <!-- hier soll die collection rein -->
6                    </div> 
7            </body>
8    </html>

Code

 1    // Ein MyView, der das Model rendert. ZB einen Track in einer Playliste.
 2    MyView = Backbone.View.extend({
 3            template: _.template( $( "#child_template" ).html() ),  // das Template
 4            render: function() {
 5                    var mod = this.model.toJSON();  // JS object des Models erstellen
 6                    var html = this.template( mod );  // In Template überführen
 7                    $( this.el ).append( html );  // Template an Element dranhängen
 8                    return this;
 9            },
10            events: {
11                    "click .mybutton": "doSomething"
12            },
13            doSomething: function( ){
14                    alert("HUP HUP");
15            }
16        });
17
18        // So erstellt ihr den MyView
19
20        _.each( data, function( singledata ) {
21                var mymodel = new MyModel( singledata );  // MyModel ist das Model
22                var myview = new MyView({
23                        model: mymodel,
24                        el: $( "#id" )  // die Collection soll ja in dieses div gerendert werden
25                });
26                mycollection.add( mymodel );
27        });

Verhalten

Ein Klick auf ein Element mit der Klasse mybutton triggert den Eventhandler für alle Views anstatt für einen.

Auflösung

Die Events in Backbone werden an ein Element im DOM gebunden. Das heißt:

  1. Das DOM Element muss (im DOM) existieren, damit überhaupt Events getriggert werden.
  2. Teilen sich mehrere Views dasselbe DOM Element, wird ein Event unter Umständen mehr als einmal getriggert. Kommt auf den CSS Selector an.

Punkt 1 passt in eurem Code, das Element div#id existiert von Anfang an und die Events werden ja auch getriggert. Punkt 2 allerdings ist das Problem. Vereinfacht gesagt sucht Backbone nach Elementen, sobald ein Event getriggert wird. In eurem Fall verwendet es dabei den CSS Selector .mybutton. Die Suche startet beim angegebenen el, welches bei euren Views dasselbe ist. Backbone findet also Repräsentationen aller Views anstatt nur eine und triggert den Eventhandler entsprechend oft.

 1    // Die Suche nach .mybutton ab div#id findet alle Bilder
 2    <div id="#id">
 3            <div id="1" class="child">
 4                    <img class="mybutton" />
 5            </div>
 6            <div id="2" class="child">
 7                    <img class="mybutton" />
 8            </div>
 9            <div id="3" class="child">
10                    <img class="mybutton" />
11            </div>
12    </div>

Ihr könntet jetzt natürlich die ID eures Models in den CSS Selector der Events einbauen, das würde helfen. Lieber nicht machen und weiterlesen!

Verbesserung

Eine elegantere Variante, die auch im Todo.js Tutorial verwendet wird, ist einen View für die Collection zu erstellen und dort die einzelnen Model-Views zu verwalten.

 1    CollectionView = Backbone.View.extend({
 2            initialize: function() {
 3                    this.model.bind( "add", this.modelAdd, this);
 4                    this.render();
 5            },
 6            render: function() {
 7                    //...
 8            },
 9            modelAdd: function( mymodel ) {
10                    var myview = new MyView({
11                            model: mymodel  // WICHTIG: Kein el mehr angeben, dann erzeugt Backbone selbst eines (default: <div></div>)
12                        });
13                        $( this.el ).append( myview.render().el );  // erzeugtes Element anhängen
14            }
15        });
16
17        myCollectionView = new CollectionView({
18                model: mycollection,
19                el: $( "id" )  // Hier ist euer el
20        });
21
22        // So wird alles gerendert
23
24        _.each( data, function( singledata) {
25                var mymodel = new MyModel( singledata );
26                mycollection.add( mymodel );
27        });

Beim mycollection.add() wird die Funktion modelAdd im View der Collection ausgeführt und das neue Model automatisch gerendert. Die Events werden wie gewünscht getriggert, da jeder View sein eigenes “Root Element” hat. Problem gelöst und der Code sieht auch noch schöner aus als vorher.