ragulka
ragulka

Reputation: 4342

Grouped collection and views with Backbone.Marionette

I have a Backbone collection of documents that I want to group by month, so that all the documents that are created within the same month, are grouped together. I know that I can basically achieve this with:

var byMonth = documents.groupBy(function(doc){
  return this.get('date').getMonth()
});

Now that I have the byMonth array, what is the best way to set up a backbone view that automatically updates when items are added to the collection, a date on one of the documents changes, etc - so that the document will automatically move to to the correct group and the views are updated accordingly?

PS. I also want to show aggregate data about each month (for example, the number of documents, etc).

Marionette's CompositeView is ideal for such things, but I'm not sure if and how I can make it work with grouped collections?

Upvotes: 2

Views: 930

Answers (2)

jackocnr
jackocnr

Reputation: 17416

I've thought about this a lot, and don't have a perfect answer, but here are my notes (brain-dump) in case they help / spark any new ideas for you:

Best way to display (and auto-update) a collection broken up into 3 sections/groups e.g. this-week, next-week and upcoming

  1. Filter the collection outside the View: Create filtered sub-collections in the Controller, and pass one to each CollectionView. Then there's 2 options for modifying the collection:
    • A) directly modify the 3 sub-collections
      • Pro: auto-update will work, no filtering logic required in CollectionView, emptyView will work, it would be easy to have a count at the top of each section.
      • Con: have to pass around these 3 collections everywhere we may want to modify them, and have this extra logic when adding/removing an event: if (event.time<next-week) {...} else if (event.time<upcoming) {...} else {...}. Could abstract all this into a new entity, which has refs to all 3 collections, and has simple add/remove methods containing the above logic.
      • Another con: items can't move between these sub-collections, which could be critical, if the groupings are time-specific. Tho I guess this would only happen on a full re-render anyway, so you could have an updateSubCollections method and call that onBeforeRender or whatever.
    • B) only modify the parent collection - requires a listener on the main collection for manually re-filtering each sub-collection in the Controller on add/remove, and manually re-rendering all 3 list views.
      • Pro: CollectionView is real simple - no filtering logic etc, emptyView just works, it would be easy to have a count at the top of each section.
      • Con: you lose the auto-update magic (when a collection changes, it's collectionView automatically updates), and requires a dumb (costly) re-render of the whole collectionView everytime
  2. Filter the collection inside the View: Pass the full collection to each CollectionView, and override appendHTML method (or showCollection) to only display the ones that satisfy a condition.
    • Pro: get the auto-update magic, and still only dealing with a single collection everywhere in the code
    • Con: Would require extra logic to show a count at the top of each section. Requires some extra logic to show the empty view or not, AND may have other consequences as I doubt CollectionViews were designed for this… they may be doing clever stuff with all the itemViews in the background - it could be inefficient - requires research. Look through source and find best (highest level) function to override to not bother processing the items that are not relevant
  3. Single view option: Filter the collection inside the view's appendHtml() function by manually appending each view in the right section of the page.
    • Pro: all the logic in one place, but probably not the right place (the CollectionView). Auto-update works. Still only dealing with a single collection everywhere in the code.
    • Con: would require extra logic to show a count at the top of each section. Would need to handle emptyView manually for each section, which isn't that hard - in your template just define all of your sections, containing empty messages, and then in your appendHtml function, when you figure out which section the item belongs to, just hide the empty message for that section before you append it. Problem is then how to hide empty sections when items are removed from the collection. Only way is to listen for the remove event on the collection, and check all sections to see if they are empty? Or just re-render the whole thing.

When I first joined my current team, they had some old code which implemented option 1B, which I thought was horrible. Then when I had to implement something similar I went with option 3, which worked but got a bit messy with time. Next time I think I'm going to try option 1A, but it's currently untested...

Let me know what you ended up doing!

Edit: couple of relevant issues on the Marionette project on Github:

Upvotes: 1

Scott Puleo
Scott Puleo

Reputation: 3684

You could create a new byMonth collection and bind a function to reset it every time the documents collection changes.

Controller

App.Controller = function(){
  this.documents = new DocumentCollection();
  this.byMonthCollection = new ByMonthCollection(); 
  this.byMonthView = new ByMonthView({collection: this.byMonthCollection});
}

_.extend(App.Controller.prototype, {
  start: function() {
    this.documents.bind("change", _.bind(this.groupByMonth, this));
    this.documents.fetch();
  },
  groupByMonth: function() {
    var grouped = documents.groupBy(function(doc){
                    return this.get('date').getMonth() }); 

    this.byMonthCollection.reset(grouped);
  }
}

Upvotes: 0

Related Questions