Search code examples
javascriptbackbone.jshandlebars.jsmarionette

How to render a collection with a Marionette's ItemView?


I'm trying to render the response from an API (JSON) with Backbone.Marionette.ItemView. Not sure why it is not working.

I'm using marionette v2.4.7 (on purpose);

Here is the handlebars template:

<script id="feed-post" type="text/x-handlebars-template">
  {{#each posts}}
    <a href="#"><img src="{{author.image_url}}" alt=""></a>
    <a href="#"><p>{{author.name}}</p></a>
    <span>TODO TIMESTAMP</span>
    <p>{{body}}</br>{{topic_type}}</p>
  {{/each}}
</script>

Here is my full app.js (all Backbone logic in this file);

// Model
var Post = Backbone.Model.extend({
  defaults: {
    authorPic:      'Unknown',
    authorName:     'Unknown',
    timestamp:      'Unknown',
    body:           'Not available',
    comments:       '0'
  }
});

// Collection
var Posts = Backbone.Collection.extend({
  model: Post,
  url: 'http://localhost:4321/blogposts',
  initialize: function(){
    this.fetch();
  }
});

// View
var PostView = Marionette.ItemView.extend({
  el: '#content',
  template: Handlebars.compile($("#feed-post").html()),
});

//Config
var chunkPosts = new Posts();
var myview = new PostView({collection: chunkPosts});

Also, I tried to console.log the view and it looks like the models are in there.

https://i.imgur.com/wg4NIZr.png


Solution

  • This answer is tailored to Marionette v2.4.7. LayoutView and ItemView were merged and renamed to View back in v3.0.0.


    From the doc on ItemView:

    Rendering this view will convert the someCollection collection in to the items array for your template to use.

    You are using posts in your template while the doc says it will be called items.

    As a reference, here's the exact code doing that in the ItemView source:

    // Serialize the model or collection for the view. If a model is
    // found, the view's `serializeModel` is called. If a collection is found,
    // each model in the collection is serialized by calling
    // the view's `serializeCollection` and put into an `items` array in
    // the resulting data. If both are found, defaults to the model.
    // You can override the `serializeData` method in your own view definition,
    // to provide custom serialization for your view's data.
    serializeData: function() {
      if (!this.model && !this.collection) {
        return {};
      }
    
      var args = [this.model || this.collection];
      if (arguments.length) {
        args.push.apply(args, arguments);
      }
    
      if (this.model) {
        return this.serializeModel.apply(this, args);
      } else {
        return {
          items: this.serializeCollection.apply(this, args)
        };
      }
    },
    

    The last lines show that for a collection, a new object with items as the only attribute is returned.

    It's mentioned that you can override the serializeData function, more information and examples are available in the doc.


    You still need to call render on the view and since the collection's fetch is async, you won't have items out of the box so you should wire a listener.

    First, don't fetch in the initialize of a collection, it makes the collection pretty much useless for any other use-case.

    var Posts = Backbone.Collection.extend({
      model: Post,
      url: 'http://localhost:4321/blogposts',
    });
    

    Listen for the collection sync event, then fetch within the view instead.

    var PostView = Marionette.ItemView.extend({
      el: '#content',
      template: Handlebars.compile($('#feed-post').html()),
      initialize: function () {
        this.listenTo(this.collection, 'sync', this.render);
        this.collection.fetch();
      },
    });
    

    Marionette even offers collectionEvents:

    var PostView = Marionette.ItemView.extend({
      // ...snip...
      collectionEvents: {
        "sync": "render"
      }
      // ...snip...
    });