Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find a Backbone.js View if you know the Model?

Tags:

backbone.js

Given a page that uses Backbone.js to have a Collection tied to a View (RowsView, creates a <ul>) which creates sub Views (RowView, creates <li>) for each Model in the collection, I've got an issue setting up inline editing for those models in the collection.

I created an edit() method on the RowView view that replaces the li contents with a text box, and if the user presses tab while in that text box, I'd like to trigger the edit() method of the next View in the list.

I can get the model of the next model in the collection:

// within a RowView 'keydown' event handler
var myIndex = this.model.collection.indexOf(this.model);
var nextModel = this.model.collection.at(myIndex+1);

But the question is, how to find the View that is attached to that Model. The parent RowsView View doesn't keep a reference to all the children Views; it's render() method is just:

this.$el.html(''); // Clear
this.model.each(function (model) {
    this.$el.append(new RowView({ model:model} ).render().el);
}, this);

Do I need to rewrite it to keep a separate array of pointers to all the RowViews it has under it? Or is there a clever way to find the View that's got a known Model attached to it?

Here's a jsFiddle of the whole problem: http://jsfiddle.net/midnightlightning/G4NeJ/

like image 214
MidnightLightning Avatar asked Jul 13 '12 15:07

MidnightLightning


2 Answers

It is not elegant to store a reference to the View in your model, however you could link a View with a Model with events, do this:

// within a RowView 'keydown' event handler
var myIndex = this.model.collection.indexOf(this.model);
var nextModel = this.model.collection.at(myIndex+1);
nextModel.trigger('prepareEdit');

In RowView listen to the event prepareEdit and in that listener call edit(), something like this:

this.model.on('prepareEdit', this.edit);
like image 116
Daniel Aranda Avatar answered Oct 16 '22 17:10

Daniel Aranda


I'd say that your RowsView should keep track of its component RowViews. The individual RowViews really are parts of the RowsView and it makes sense that a view should keep track of its parts.

So, your RowsView would have a render method sort of like this:

render: function() {
    this.child_views = this.collection.map(function(m) {
        var v = new RowView({ model: m });
        this.$el.append(v.render().el);
        return v;
    }, this);
    return this;
}

Then you just need a way to convert a Tab to an index in this.child_views.


One way is to use events, Backbone views have Backbone.Events mixed in so views can trigger events on themselves and other things can listen to those events. In your RowView you could have this:

events: {
    'keydown input': 'tab_next'
},
tab_next: function(e) {
    if(e.keyCode != 9)
        return true;
    this.trigger('tab-next', this);
    return false;
}

and your RowsView would v.on('tab-next', this.edit_next); in the this.collection.map and you could have an edit_next sort like this:

edit_next: function(v) {
    var i = this.collection.indexOf(v.model) + 1;
    if(i >= this.collection.length)
        i = 0;
    this.child_views[i].enter_edit_mode(); // This method enables the <input>
}

Demo: http://jsfiddle.net/ambiguous/WeCRW/

A variant on this would be to add a reference to the RowsView to the RowViews and then tab_next could directly call this.parent_view.edit_next().


Another option is to put the keydown handler inside RowsView. This adds a bit of coupling between the RowView and RowsView but that's probably not a big problem in this case but it is a bit uglier than the event solution:

var RowsView = Backbone.View.extend({
    //...
    events: {
        'keydown input': 'tab_next'
    },
    render: function() {
        this.child_views = this.collection.map(function(m, i) {
            var v = new RowView({ model: m });
            this.$el.append(v.render().el);
            v.$el.data('model-index', i); // You could look at the siblings instead...
            return v;
        }, this);
        return this;
    },
    tab_next: function(e) {
        if(e.keyCode != 9)
            return true;
        var i = $(e.target).closest('li').data('model-index') + 1;
        if(i >= this.collection.length)
            i = 0;
        this.child_views[i].enter_edit_mode();
        return false;
    }
});

Demo: http://jsfiddle.net/ambiguous/ZnxZv/

like image 39
mu is too short Avatar answered Oct 16 '22 15:10

mu is too short