Search code examples
javascriptbackbone.jstimelinebackbone.js-collections

How to render a Backbone collection with headers between items


I have a Backbone collection in my app that is basically a timeline. Every item in the collection has a date field and the collection is sorted by it. Items are dynamically loaded and added to this collection (when scolling).

I want to render this collection, but I also need to render a <div> between the items as a day header. Example:

[{"date": new Date("2015-01-02T12:00"),
  "text: "Message 4"},
 {"date": new Date("2015-01-02T06:00"),
  "text: "Message 3"},
 {"date": new Date("2015-01-01T23:00"),
  "text: "Message 2"},
 {"date": new Date("2015-01-01T20:00"),
  "text: "Message 1"}
]

I want to render something like this:

<div class="timeline">
  <div class="day">2015-01-02</div>
  <div class="message">Message 4</div>
  <div class="message">Message 3</div>
  <div class="day">2015-01-01</div>
  <div class="message">Message 2</div>
  <div class="message">Message 1</div>
</div>

Or like this (nested):

<div class="timeline">
  <div class="day">2015-01-02
    <div class="message">Message 4</div>
    <div class="message">Message 3</div>
  </div>
  <div class="day">2015-01-01
    <div class="message">Message 2</div>
    <div class="message">Message 1</div>
  </div>
</div>

I have been looking at Backbone.Projections but it seems that I would need to manually create a lot of filtered collections (one collection for each day) and then put all those daily collections into yet another collection.

I have also looked at the Backbone.Collection.groupBy but I don't see how I can use that to render views, let alone how to keep the groups updated when new items are added to the timeline.

Is there a good way to render a timeline collection like this? Any plugins that would help?


Solution

  • One of the strengths of Backbone is that it does not force you to use a predefined pattern. In your case, you could group the models by day, generate the HTML for each group and then push the result to the DOM.

    Assuming your models and collection are defined like this, for convenience:

    var M =  Backbone.Model.extend({
        day: function() {
            var d = this.get('date'), 
                month = d.getMonth()+1,
                day = d.getDate();
            if (month<10) month = '0'+month;
            if (day<10) day = '0'+day;
    
            return [d.getFullYear(), month, day].join('-');
        }
    });
    var C = Backbone.Collection.extend({
        model: M
    });
    

    Your view could look like

    var V = Backbone.View.extend({
        render: function() {
            var rows;
    
            rows = this.collection.chain().groupBy(function (m) {
                return m.day();
            }).map(function (models, dt) {
                // you would use a template here
                // hard coded strings will do for now
    
                var html = '<div class="day">' + dt + '</div>';
                _.each(models, function (m) {
                    html+= '<div class="message">' + m.get('text') + '</div>';
                });
    
                return html;
            }).value();
    
            this.$el.html(rows.join(''));
        }
    });
    

    And a Fiddle http://jsfiddle.net/nikoshr/swbm8ax5/4/