Search code examples
javascripthtmljsonbackbone.jsmodel-view-controller

Backbone: Collection not rendered in View even though it's fetched


I know that there are a few questions around regarding this, but the answers are not very clear for me to implement. That's why I'm asking this question again so I can have a clear and simple answer.

I've always had trouble with Collection in Backbone, especially populating it with JSON data.

I can't seem to get the collection to render in the View, even though in firebug I can see that, it's being fetched from the server, but the screen is still empty. Also, when I do a console.log('callers: ', this.callerList), it returns an object with models=[0]. But when I expand the object, models is full of data from the JSON file. What's going on with Backbone and it's confusing results?

Can someone please explain to me how to do it? I've been battling this for ages and I can't get my head around it.

Many Thanks

JS:

(function($, window) {

  // model
  var CallerModel = Backbone.Model.extend({});

  // collection
  var CallersList = Backbone.Collection.extend({
      model: CallerModel,
      url: 'js/json/callers.json'
  });

  // view
  var CallerView = Backbone.View.extend({
      el: '.caller-app',

      template: _.template($('#callers-template').html()),

      initialize: function() {
          this.callerList = new CallersList();
          this.callerList.fetch();
          this.callerList.bind('reset', this.render);

          console.log('caller: ', this.callerList);
      },

      render: function(e) {

                  console.log('RENDER');

          _.each(this.collection.models, function(caller) {
              this.$el.append(this.template(caller.toJSON()));
                          console.log('callerList: ', caller);
          }, this);

          return this;
      }
  });

  // start
  var callerView = new CallerView();

}(jQuery, window));

HTML:

<!-- wrapper -->
<div class="wrapper">
    <h1>Missed Calls App</h1>

    <div class="caller-app"></div>

</div>
<!-- wrapper -->


<!-- templates -->
<script type="text/template" id="callers-template">
    <div class="caller">
        <h2><%= title %> <%= name %> called</h2>
        <h3>From <%= agency %></h3>
        <p>When: <%= when %></p>
        <p>Contact: <%= tel %></p>
        <p>Says:"<%= message %>"</p>
    </div>
</script>
<!-- templates -->

JSON:

[
  {
      "id": 1,
      "title": "Mrs",
      "name": "Mui",
      "agency": "Ryuzanpaku Dojo",
      "when": "evening",
      "tel": "0207 123 45 67",
      "message": "Check your availability"
  },
  {
      "id": 2,
      "title": "Mrs",
      "name": "Shigure",
      "agency": "Ryuzanpaku Dojo",
      "when": "evening",
      "tel": "0207 123 45 67",
      "message": "Check your availability"
  }
]

Solution

  • You haven't actaully assigned a collection to your CallerView, in addition when you iterate though the collection you should be using this.collection.models instead of this.model.models

    For example when initializing you caller list initialize: function() {

        initialize: function() {
        this.collection = new CallersList();
        this.collection.fetch();
        this.collection.bind('reset', this.render);
       }
    

    And when rendering

    render: function(e) {
    
              _.each(this.collection.models, function(caller) {
                  this.$el.append(this.template(caller.toJSON()));
    
              }, this);
    
              return this;
          }
    

    Here's a link to a jsbin

    Some additional points

    In general you want to decouple your code as much as possible. To this end it is probably better to declare and initialize your collection outside of your view and then pass it in. This also has the advantage of making your code more reusable, for example say you wanted to render a second list of calls (let say recent calls), you can now just create a second instance of your view passing in a collection and element.

    For example

    var missedCalls = new CallersList();
    var callerView = new CallerView({collection : missedCalls, el: '#missedCalls' });
    missedCalls.fetch();
    
    
    var recentCalls = new CallerList(); //you probably want to use a different url
    var recentCallersView = new CallerView({collection : recentCalls, el:'#recentCalls'}); 
    recentCalls.fetch();
    

    Another point worth mentioning, currently you are rendering all items in your collection for each fetch, including any that have been already rendered. You might want either empty your el before rendering or listen to the add event instead and render each item individually as it's added. In addition it's worth pointing out that fetch isn't really meant to be used to load data on page load, from the documentation

    Note that fetch should not be used to populate collections on page load — all models needed at load time should already be bootstrapped in to place. fetch is intended for lazily-loading models for interfaces that are not needed immediately: for example, documents with collections of notes that may be toggled open and closed.