9

I have a BoardView containing a CellCollection of CellModels. I fetch the collection from the db and then create the CellViews.

This all works swimmingly until I try to access a CellModel via a click event on the BoardView. I can't get to the underlying models at all... only the views. Is there a way to do this?

I've attempted to include the relevant code below:

CellModel = Backbone.Model.extend({});

CellCollection = Backbone.Collection.extend({
    model : CellModel
});

CellView = Backbone.View.extend({
    className : 'cell',
});

BoardView = Backbone.View.extend({
    this.model.cells = new CellCollection();

    render : function() {
        this.cellList    = this.$('.cells');
        return this;
    },

    allCells : function(cells) {
        this.cellList.html('');
        this.model.cells.each(this.addCell);
        return this;
    },

    addCell : function(cell) {
        var view = new Views.CellView({
            model : cell
        }).render();

        this.cellList.append(view.el);
    },

    events : {
        'click .cell' : 'analyzeCellClick',
    },

    analyzeCellClick : function(e) {
        // ?????????
    }
});

I need the click to "happen" on the BoardView, not the CellView, because it involves board-specific logic.

Arslan Ali
  • 17,418
  • 8
  • 58
  • 76
cosmo_kramer
  • 729
  • 2
  • 7
  • 12

3 Answers3

12

Good question! I think the best solution would be to implement an

EventBus aka EventDispatcher

to coordinate all events among the different areas of your application.

Going that route seems clean, loosely coupled, easy to implement, extendable and it is actually suggested by the backbone documentation, see Backbone Docs

Please also read more on the topic here and here because (even though I tried hard) my own explanation seems kind of mediocre to me.

Five step explanation:

  1. Create an EventBus in your main or somewhere else as a util and include/require it

     var dispatcher = _.clone(Backbone.Events); // or _.extends 
    
  2. Add one or more callback hanlder(s) to it

     dispatcher.CELL_CLICK = 'cellClicked'
    
  3. Add a trigger to the Eventlistener of your childView (here: the CellView)

     dispatcher.trigger(dispatcher.CELL_CLICK , this.model);
    
  4. Add a Listener to the Initialize function of your parentView (here: the BoardView)

     eventBus.on(eventBus.CARD_CLICK, this.cardClick);
    
  5. Define the corresponding Callback within of your parentView (and add it to your _.bindAll)

     cellClicked: function(model) {
     // do what you want with your data here
     console.log(model.get('someFnOrAttribute')
     }
    
Community
  • 1
  • 1
Bruiser
  • 11,767
  • 5
  • 34
  • 45
11

I can think of at least two approaches you might use here:

  1. Pass the BoardView to the CellView at initialization, and then handle the event in the CellView:

    var CellView = Backbone.View.extend({
        className : 'cell',
    
        initialize: function(opts) {
            this.parent = opts.parent
        },
    
        events : {
            'click' : 'analyzeCellClick',
        },
    
        analyzeCellClick : function() {
            // pass the relevant CellModel to the BoardView
            this.parent.analyzeCellClick(this.model);
        }
    });
    
    var BoardView = Backbone.View.extend({
        // ...
    
        addCell : function(cell) {
            var view = new Views.CellView({
                model : cell,
                parent : this
            }).render();
    
            this.cellList.append(view.el);
        },
    
        analyzeCellClick : function(cell) {
            // do something with cell
        }
    });
    

    This would work, but I prefer to not have views call each other's methods, as it makes them more tightly coupled.

  2. Attach the CellModel id to the DOM when you render it:

    var CellView = Backbone.View.extend({
        className : 'cell',
    
        render: function() {
            $(this.el).data('cellId', this.model.id)
            // I assume you're doing other render stuff here as well
        }
    });
    
    var BoardView = Backbone.View.extend({
        // ...
    
        analyzeCellClick : function(evt) {
            var cellId = $(evt.target).data('cellId'),
                cell = this.model.cells.get(cellId);
            // do something with cell
        }
    });
    

    This is probably a little cleaner, in that it avoids the tight coupling mentioned above, but I think either way would work.

nrabinowitz
  • 55,314
  • 10
  • 149
  • 165
  • OP here. both solutions worked flawlessly... and #1 is perfect for my purposes. thanks very much. – cosmo_kramer Sep 21 '11 at 19:46
  • Great - if you're happy with the answer, you can accept it by clicking on the checkbox underneath the score :). – nrabinowitz Sep 21 '11 at 19:57
  • 6
    Solution 2, in my opinion, is not a good idea. The whole point of Backbone is to get data from server and render it in the client view, and not to get data from the view and send back. It works, but it's not a nice way. – Sơn Trần-Nguyễn Mar 01 '12 at 02:49
6

I would let the CellView handle the click event, but it will just trigger a Backbone event:

var CellView = Backbone.View.extend({
    className : 'cell',

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

    events : {
        'click' : 'analyzeCellClick',
    },

    analyzeCellClick : function() {
        this.trigger('cellClicked', this.model);
    }
});

var BoardView = Backbone.View.extend({
    // ...

    addCell : function(cell) {
        var view = new Views.CellView({
            model : cell
        }).render();

        this.cellList.append(view.el);
        view.bind('cellClicked', function(cell) {
              this.analyzeCellClick(cell);
            };
    },

    analyzeCellClick : function(cell) {
        // do something with cell
    }
});
Sơn Trần-Nguyễn
  • 2,188
  • 1
  • 26
  • 30
  • 1
    Nice solution..But there are few mistakes in your code. 1. view.bind('cellClicked' this.analyzeCellClick) 2. Just try out the context we get in analyzeCellClick. It will be of view because this method is triggered by cellview So rather use closures or use Function.bind. – Sachin Jain Mar 03 '13 at 02:27
  • this approach seems more consistent with how i understand how Backbone is supposed to work. or am i wrong? – b_dubb Aug 05 '13 at 17:11