Search code examples
javascriptbackbone.jsmarionette

Backbone listen to custom collection?


// build a collection of players with this team ID
this.collection = new PlayersCollection(App.data.players.where({team_id: this.model.id}));

When I do this it renders this.collection = App.data.players; it has to do with the collection being a new collection, how do I listen to a new collection?

With marionette I am passing a collection of players into a collection of teams with collectionViews, compositeViews, and itemViews.

Teams collectionView > Team compositeView > Player itemView

Inside the team compositeView I define this.collection as a new PlayersCollection that matches the particular teams ID. This renders perfect, but adding a new player I can't get the view to re-render without refreshing the page.

addPlayer: function(e) {
    e.preventDefault();

    var $newPlayer = this.$el.find('input.player_name');

    var Player = {
        player_name : $newPlayer.val(), 
        team_id     : this.model.id
    }

    App.data.players.create(Player);
    $newPlayer.val('');

},

Here is a majority of the code. I am trying to add/create a new player and update the view with listenTo but since this is not a traditional way to define the collection it seems I have to find a way to bind it?

Teams.js:

var Marionette = require('backbone.marionette'),
    playerView = require('./player'),
    PlayersCollection = require('../../collections/players');

var teamView = Marionette.CompositeView.extend({
    events: {
        'submit #AddPlayer': 'addPlayer'
    },
    initialize: function() {
        // anytime something within this specific team changes, render
        this.listenTo(this.model, 'change', this.render);

        // build a collection of players with this team ID
        this.collection = new PlayersCollection(App.data.players.where({team_id: this.model.id}));

    },
    addPlayer: function(e) {
        e.preventDefault();

        var $newPlayer = this.$el.find('input.player_name');

        var Player = {
            player_name : $newPlayer.val(), 
            team_id     : this.model.id
        }

        App.data.players.create(Player);
        $newPlayer.val('');

    },
    itemView: playerView,
    appendHtml: function(collectionView, itemView){
        collectionView.$('.the-players').append(itemView.el);
    }
});

module.exports = CollectionView = Marionette.CollectionView.extend({
    initialize: function() {
        this.listenTo(this.collection, 'change', this.render);
    },
    itemView: teamView
});

Players.js:

var Marionette = require('backbone.marionette');

module.exports = playerView = Marionette.ItemView.extend({
    className: 'players-wrap row',
    template: require('../../../templates/teams/player.hbs'),
    events: {
        'mouseenter .player-image': 'playerOn',
        'mouseleave .player-image': 'playerOff',
        'click .player-image': 'playerClicked',
        'keydown': 'on_keypress',
        'click .one-point': 'onePoint',
        'click .two-point': 'twoPoint',
        'click .three-point': 'threePoint'
    },
    initialize:function() {
        _.bindAll(this, 'on_keypress');
        $(document).bind('keydown', this.on_keypress);
        this.listenTo(this.model, 'change', this.render);
        this.$el.attr('data-player', this.model.get('player_name').replace(/\s+/g,"_").toLowerCase());
        window.play = this.model;
    },
    playerOn:function(e) {
        this.$el.addClass('hover');
        this.findPlayer(this.model.get('player_name'), 'add');      
    },
    playerOff:function() {
        this.$el.removeClass('hover');
        this.findPlayer(this.model.get('player_name'), 'remove');
    },
    playerClicked:function() {
        this.$el.toggleClass('hover-clicked');
        this.findPlayer(this.model.get('player_name'), 'toggle'); 
    },
    findPlayer:function(name, action) {
        var player = $('.data-wrap .player-data[data-player='+ name.replace(/\s+/g,"_").toLowerCase() +']');
        if(action === 'toggle') { player.toggleClass('show-confirmed-clicked') }
        if(action === 'add')    { player.addClass('show-player-data') }
        if(action === 'remove') { player.removeClass('show-player-data') }
    },
    onePoint:function(e) {
        var addStat = parseInt(this.model.get('points')) + 1;        
        this.model.set('points', addStat);
        this.model.save();
    },
    twoPoint:function(e) {
        var addStat = parseInt(this.model.get('points')) + 2;        
        this.model.set('points', addStat);
        this.model.save();
    },
    threePoint:function(e) {
        var addStat = parseInt(this.model.get('points')) + 3;        
        this.model.set('points', addStat);
        this.model.save();
    },
    on_keypress:function(e) {
        this.keyStat(e, 49, 'points', 1);   // 1 point
        this.keyStat(e, 50, 'points', 2);   // 2 points
        this.keyStat(e, 51, 'points', 3);   // 3 points
        this.keyStat(e, 82, 'rebounds', 1); // R rebounds
        this.keyStat(e, 83, 'steals', 1); // S steals
    },
    keyStat:function(e, keyVal, stat, number) {
        if(e.keyCode == keyVal && !e.ctrlKey) { 
            if(this.$el.hasClass('hover')) {
                var addStat = parseInt(this.model.get(stat)) + number;        
                this.model.set(stat, addStat);
                this.model.save();
            }
        }
    },
    templateHelpers:function(){
        return {
            name_format: this.model.get('player_name').replace(/\s+/g, '_').toLowerCase()
        }
    }
});

Solution

  • I can't find the place where you adding your new model to the CompositeView's collection? I found only App.data.players.create(Player); which is not the collection of CompositeView.

    If App.data.players is another Backbone.Collection which you need to keep in sync with the remote server and because of that you are using create to persist it to the server, then change addPlayer like this:

    addPlayer: function(e) {
        e.preventDefault();
    
        var $newPlayer = this.$('input.player_name');
    
        var Player = {
            player_name : $newPlayer.val(), 
            team_id     : this.model.id
        }
    
        var newPlayer = App.data.players.create(Player);
        this.collection.add(newPlayer);
        $newPlayer.val('');
    
    }, 
    

    CompositeView will catch the "add" event of collection and will append new itemView. Please try and let me know if it helps.

    Just a note: After review of your code I found that appendHtml in Teams.js can be removed with itemViewContainer property with value of .the-players which will improve performance of rendering.