Using Marionette to group items in a collection view
Asked Answered
R

3

7

I'm building an application using backbone and marionette.js. I'm planning on using a collection view to present some items and then allow them to be filtered, sorted and grouped.

I was wondering if there are any good design ideas for actually appending the html in a grouped fashion. I have a few ideas but I was wondering if someone might have input on which would be better design.

My first idea is to change the appendHtml method on the collection view, and if grouping is enabled, I can have the appendHtml function either find or create the child group's bin and place the child view in it.

appendHtml: function(collectionView, itemView, index){
  var $container = this.getItemViewContainer(collectionView);

  // get group from model
  var groupName = itemView.model.get("group");

  // try to find group in child container
  var groupContainer  = $container.find("." + groupName);

  if(groupContainer.length === 0){
    // create group container
    var groupContainer = $('<div class="' + groupName + '">')
    $container.append(groupContainer);
  }

  // Append the childview to the group
  groupContainer.append(itemView);
}

My second idea is to break apart the collection into groups first and then maybe render multiple views... This one seems like it might be more work, but might also be a bit better as far as the code structure is concerned.

Any suggestions or thought eliciting comments would be great!

Thanks

Rewarding answered 12/2, 2013 at 18:15 Comment(5)
I like your first approach better, and it is probably easier too. Presentation stuff should be in the view, and it seems strange to split up a collection just to display it differently. At some point you might want a button to toggle whether the list view is grouped, then multiple collections might get in the way.Dahlberg
Yes, that is the one I went with... I do need to be a little bit more careful that way though... because I wanted to add headings to each group... which then means I need to make sure I keep track of those as well. Which is unfortunate because Marionette does a great job of keeping track of the add/removing items w/o much extra usually...Rewarding
I wonder if it would help if you add a comparator to your collection to sort by group. Then when there is any add/remove/reset change, in the view you could do a sort of post processing step, after marionette does the insert/remove, to insert the headers... I guess there are 1000 ways to do anything.Dahlberg
As I put this together... I did a test implementation with the grouping the first way... and I am not sure I like it... the problem is that I will be showing a filtered collection which can have items added/removed/reset. If I just throw the headers in on render, then if a new filter is defined which removes all the groups items... the header will remain unless i redraw everything...Rewarding
Hi MattyP, just want to let you know that i needed to modified the last line to groupContainer.append(itemView.el); in order for it to work. I'm not sure if it is because of versions difference. Hopefully it helps someone.Blanchblancha
C
7

Maybe not exactly what you're looking for, but here's a somewhat related question:

Backbone.Marionette, collection items in a grid (no table)

My solution to that issue -- one fetched collection that could be rendered as a list or a grid ("items grouped in rows") was to use _.groupBy() in a "wrapper" CompositeView and pass modified data down the chain to another CompositeView (row) and then down to an ItemView.

Views.Grid = Backbone.Marionette.CompositeView.extend({
    template: "#grid-template",
    itemView: Views.GridRow,
    itemViewContainer: "section",
    initialize: function() {
        var grid = this.collection.groupBy(function(list, iterator) {
            return Math.floor(iterator / 4); // 4 == number of columns
        });
        this.collection = new Backbone.Collection(_.toArray(grid));
    }
});

Here's a demo:

http://jsfiddle.net/bryanbuchs/c72Vg/

Confute answered 13/2, 2013 at 15:59 Comment(1)
This is kind of the idea I was thinking about for my second idea... the one thing that I would like to do is still handle filtering... and stuff while things are grouped... So I imagine I would have to wire all of the sub-collections to listen for add/remove/reset of the parents... I actually start to like this idea more as I think about it...Rewarding
F
4

I've done both of the things your suggesting, and they both work well. It largely comes down to which one you prefer and maybe which one fits your scenario better.

If you have data that is already in a grouped hierarchy, using one of the many hierarchical model / collection plugins or your own hierarchy code, then the idea of rendering a list of groups, with each group rendering a list of items is probably easier.

If you have data that is flat, but contain a field that you will group by, then the appendHtml changes will probably be easier.

hth

Franzoni answered 13/2, 2013 at 14:24 Comment(0)
Q
1

This is in addition to Derick's and bryanbuchs' answers. My method uses a main collection view and another collection view as its childView.

Collection views have a 'addChild' method, which is called whenever a model is added to the view's collection. The 'addChild' method is responsible for rendering the child's view and adding it to the HTML for the collection view at a given index. You can see the source code on github here. I'll paste it here for simplification:

addChild: function(child, ChildView, index) {
    var childViewOptions = this.getOption('childViewOptions');
    if (_.isFunction(childViewOptions)) {
      childViewOptions = childViewOptions.call(this, child, index);
    }

    var view = this.buildChildView(child, ChildView, childViewOptions);

    // increment indices of views after this one
    this._updateIndices(view, true, index);
    this._addChildView(view, index);
    return view;
}

As you can see the 'addChild' method calls the 'buildChildView' method. This method actually builds the view.

// Build a `childView` for a model in the collection.
buildChildView: function(child, ChildViewClass, childViewOptions) {
var options = _.extend({model: child}, childViewOptions);
return new ChildViewClass(options);
}

So for your use case you can override the 'addChild' method and make a call to the original method if your grouping criteria is matched. And then in the overridden 'buildChildView' method you can pass the group (which is a subset of your collection) to its childView, which is another Marionette.CollectionView.

Example:

MyCollectionView.prototype.addChild = function(child, ChildView, index) {
    if(mycriteria){
        return ProductGroup.__super__.addChild.apply(this, arguments);
    }
};

MyCollectionView.prototype.buildChildView = function(child, ChildViewClass, 
childViewOptions) {

    options = _.extend({
        collection: "Pass your group collection which is a subset of your main collection"},
        childViewOptions
    );

    return new ChildViewClass(options);
  };
Queen answered 5/9, 2014 at 13:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.