ndequeker
ndequeker

Reputation: 7990

Backbone direct url access breaks collection

When a user goes to http://example.com/#/playlist, I want to list some songs.

It works (all songs are listed) when you first go to http://example.com and click on the link <a href="#/playlist">Playlist</a>.

But if you directly go to http://example.com/#/playlist, nothing is shown.

When I log the this.collection property, the collection is always the same (if you use the link, or directly access).

The only difference is that this.collection.each( function ( song ) {} ); in the render-function below, doesn't loop when directly accessing the URL.

This is the code:

define(
    [
        'jQuery',
        'Underscore',
        'Backbone',
        'collections/songlist',
        'views/song'
    ],

    function ($, _, Backbone, SonglistCollection, SongView)
    {
        var PlaylistView = Backbone.View.extend({

            // properties
            el: '#content',
            tagName: 'ul',
            collection: new SonglistCollection(),

            /**
             * Initialize
             */
            initialize: function()
            {
                // load songs
                this.collection.bind( 'reset', this.render(), this );
                this.collection.fetch();
            },

            /**
             * Render
             */
            render: function ()
            {
                this.$el.html('');

                console.log(this.collection);

                this.collection.each( function ( song )
                {
                    var songItem = new SongView( { model: song } );
                    this.$el.append( songItem.el );

                }, this);
            }

        });

        return new PlaylistView;

    }
);

The problem occurs here, in the 'each-loop':

render: function ()
                {
                    this.$el.html('');

                    console.log(this.collection);

                    this.collection.each( function ( song )
                    {
                        var songItem = new SongView( { model: song } );
                        this.$el.append( songItem.el );

                    }, this);
                }

UPDATE

This is my routing code:

define([
    'jQuery',
    'Underscore',
    'Backbone',
    'views/start',
    'views/playlist'
],

    function ($, _, Backbone, startView, playlistView)
    {
        var AppRouter = Backbone.Router.extend({
           routes: {
               'playlist': 'playlist',

               // default
               '*actions': 'start'
           },

           start: function ()
           {
               // call render on the module we loaded via the dependency array
               // views/items/list
               startView.render();
           },

            playlist: function ()
            {
                playlistView.render();
            },

            defaultAction: function ( actions )
            {
                // no default action, let's just log what the url was
                console.log('No route:', actions)
            }
        });

        var initialize = function ()
        {
            var app_router = new AppRouter;
            Backbone.history.start();
        }

        return {
            initialize: initialize
        };
    }

);

Upvotes: 2

Views: 306

Answers (3)

Brendan Delumpa
Brendan Delumpa

Reputation: 1145

In the MVC pattern, the view responds to changes in the model. You have that set up in your code with:

this.collection.on('reset', this.render, this);

But the problem is that you're not causing a reset that would in turn cause your view to re-render. So instead of invoking the render method in 'playlist,' change the url of the collection to the appropriate REST endpoint, then do a collection.fetch. Once you do the fetch, a 'reset' will be fired, and your render will then be invoked, properly reflecting the updated collection.

Upvotes: 0

jakee
jakee

Reputation: 18566

I assume that the problem is related to the fact that you initialize your PlayListView before returning it to require.js. Also you arbitrarily call the PlaylistView's render method before the collection is definitely done with fetching.

  1. So when you go through http://example.com:
    1. require init startView
    2. require init playListView -> collection fetch start
    3. route to startView
    4. playListView fetch returns -> collection now populated
    5. click link -> render playListView correctly
  2. and when you go through http://example.com/#/playlist:
    1. require init startView
    2. require init playListView -> collection fetch start
    3. route to playListView -> render playListView incorrectly because collection still fetching (aka there is nothing to loop)
    4. playListView fetch returns -> collection now populated

Change this line return new PlaylistView; to this return PlaylistView;

and the Router of course should be changed as well

function ($, _, Backbone, startView, **PlaylistView**) // change the variable name
...
var AppRouter = Backbone.Router.extend({
  ...
  playlistView: null, // Add attribute for the router
  ...
  playlist: function () {
    playlistView = new PlayListView();
    // No render because you bound the render to the reset of the collection !!!
  },
  ...
}
...

Now you won't be initializing your PlaylistView while something necessary is still left unloaded.

Upvotes: 0

fguillen
fguillen

Reputation: 38792

I think the problem is that when you call playlistView.render(); in the Router the playlist.collection is still not populated.

You are trusting in what the console.log(this.collection); is showing to you but you can't trust in console.log() with complex objects.

Check:

Upvotes: 1

Related Questions