Question

I'm doing my first application in backbone and i get a strange thing happening trying to attach an event.

I got this code so far:

//View for @girl, EDIT action
GirlEditView = Backbone.View.extend({
    initialize: function(el, attr) {
        this.variables = attr;
        console.log(attr);
        this.render();
    },
    render: function() {
        var template = _.template( $("#girl_edit").html(), this.variables );
        $(this.el).html( template );
        $("#edit_girl").modal('show');
    }
});

//View for @girl
GirlView = Backbone.View.extend({
    initialize: function(el, attr) {
        this.variables = attr;
        this.render();
    },
    render: function() {
        var template = _.template( $("#girl_template").html(), this.variables );
        $(this.el).html( $(this.el).html() + template );
    },
    events: {
        "click p.modify": "modify"
    },
    modify: function() {
        //calls to modify view
        new GirlEditView({el : $("#edit_girl")}, this.variables);
    }
});


//One girl from the list
Girl = Backbone.Model.extend({
    initialize: function() {
        this.view = new GirlView({el : $("#content")}, this.attributes );
    }
});

//all the girls
Girls = Backbone.Collection.extend({
    model: Girl,
});




//do magic!
$(document).ready(function() {

    //Underscore template modification
    _.templateSettings = {
        escape : /\{\[([\s\S]+?)\]\}/g,
        evaluate : /\{\[([\s\S]+?)\]\}/g,
        interpolate : /\{\{([\s\S]+?)\}\}/g
    }

    //get initial data and fill the index
    var list = [];
    $.getJSON('girls.json', function(data) {
        list = [];
        $.each(data, function(key, val) {
            list.push( new Girl(val) );
        });

        var myGirls = new Girls(list);
        console.log( myGirls.models);
    });
});

As you can see.

I'm using a collection to store all the girls and the data comes from a REST api in ruby.

Each girls create a new model instance and inside i attached a view instance.

I don't know if it's a good practice but i can't think a better way to do it.

Each view makes a content with a unique id. girl-1 girl-2 and go on.

Now, the template have a edit button. My original idea is to attack the onclick event and trigger the edit view to get rendered.

That is working as expected.

The proble so far is:

When the events triggers, all the collection (girls) fire the edit view, not the one that "owns" the rendered view.

My question is what i'm doing wrong?

Thanks a lot

Was it helpful?

Solution

All the edit-views come up because all the GirlViews are using the same el:

this.view = new GirlView({el : $("#content")}, this.attributes );

and then you render be appending more HTML:

render: function() {
    var template = _.template( $("#girl_template").html(), this.variables );
    $(this.el).html( $(this.el).html() + template );
}

Backbone events are bound using delegate on the view's el. So, if multiple views share the same el, you'll have multiple delegates attached to the same DOM element and your events will be a mess of infighting.

You have things a little backwards: models do not own views, views watch models and collections and respond to their events. You'll see this right in the documentation:

constructor / initialize new View([options])

[...] There are several special options that, if passed, will be attached directly to the view: model, collection, [...]

Generally, you create a collection, c, and then create the view by handing it that collection:

var v = new View({ collection: c })

or you create a model, m, and then create a view wrapped around that model:

var v = new View({ model: m })

Then the view binds to events on the collection or model so that it can update its display as the underlying data changes. The view also acts as a controller in Backbone and forwards user actions to the model or collection.

Your initialization should look more like this:

$.getJSON('girls.json', function(data) {
    $.each(data, function(key, val) {
        list.push(new Girl(val));
    });

    var myGirls = new Girls(list);
    var v       = new GirlsView({ collection: myGirls });
});

and then GirlsView would spin through the collection and create separate GirlViews for each model:

var _this = this;
this.collection.each(function(girl) {
    var v = new GirlView({ model: girl });
    _this.$el.append(v.render().el);
});

Then, GirlView would render like this:

// This could go in initialize() if you're not certain that the
// DOM will be ready when the view is created.
template: _.template($('#girl_template').html()),
render: function() {
    this.$el.html(this.template(this.model.toJSON());
    return this;
}

The result is that each per-model view will have its own distinct el to localize the events. This also makes adding and removing a GirlView quite easy as everything is nicely wrapped up in its own el.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top