3

I am trying to make a table grid with dynamic number of rows and column with headers using marionette.

I want a grid that looks like: http://jsfiddle.net/zaphod013/c3w61gf6/

So there are

columns = ['breakfast', 'lunch', 'dinner']

rows = ['carbs', 'proteins', 'fats']

and rest of the grid is checkboxes.

I have made the Views for column and row, but I am fairly lost at how to put them in the table, and then how to add the checkbox views.

Code I have is at http://jsfiddle.net/zaphod013/qkctrLxn/

html:

<div id="main-region"></div>

<script id="food-table" type="text/template">
    <thead id="column-id">
    </thead>
    <tbody id="row-id">
    </tbody>
</script>

<script id="food-col-item" type="text/template">
    <th><%= col %></th>
</script>

<script id="food-row-item" type="text/template">
    <td><%= row %></td>
</script>

script:

FoodManager = new Backbone.Marionette.Application();

FoodManager.addRegions({
    mainRegion: "#main-region",
});

FoodManager.FoodLayout = Backbone.Marionette.Layout.extend({
    template: "#food-table",

    regions: {
       colRegion:"#column-id",
       rowRegion:"#row-id"
    }
});

FoodManager.Col = Backbone.Model.extend({});

FoodManager.ColCollection = Backbone.Collection.extend({
                                    model: FoodManager.Col
                                });

FoodManager.Row = Backbone.Model.extend({});

FoodManager.RowCollection = Backbone.Collection.extend({
                                    model: FoodManager.Row
                                });

FoodManager.ColItemView = Marionette.ItemView.extend({
    template: "#food-col-item",
    tagName: "th",
});

FoodManager.ColView = Marionette.CompositeView.extend({
    template: "#food-table",
    tagName: "thead",
    itemView: FoodManager.ColItemView
});

FoodManager.RowItemView = Marionette.ItemView.extend({
    template: "#food-row-item",
    tagName: "th",
});

FoodManager.RowView = Marionette.CompositeView.extend({
    template: "#food-table",
    tagName: "table",
    itemView: FoodManager.RowItemView
});

FoodManager.on("initialize:after", function(){
    var columns = new FoodManager.ColCollection([
                    {col: 'Breakfast'},
                    {col: 'Lunch'},
                    {col: 'Dinner'}
            ]);
    var rows = new FoodManager.RowCollection([
                    {row: 'Carbs'},
                    {row: 'Protein'},
                    {row: 'Fats'}
            ]);
    var colListView = new FoodManager.ColView({
                            collection: columns
                        });
    var rowListView = new FoodManager.RowView({
                            collection: rows
                        });
    var foodLayout = new FoodManager.FoodLayout();    
    foodLayout.render();
    FoodManager.colRegion.show(colListView);
    FoodManager.rowRegion.show(rowListView);

    FoodManager.mainRegion.show(foodLayout);

});

 FoodManager.start();

I will really appreciate some pointers on how to go about this.

Thanks for reading through.

zaphod
  • 2,045
  • 1
  • 14
  • 18
  • What do you mean by "a dynamic number of rows and columns"? What you've described doesn't seem dynamic to me. Dynamic means that there could be a different number each time the table is rendered, depending on the data you load. The table you want, as I understand it, always has three columns and three rows. – Michael.Lumley Feb 22 '15 at 13:19
  • The html example shows 3 rows 3 columns as an rendered example. But if you see the js code, the row and column are collections, and are thus dynamic. – zaphod Feb 22 '15 at 16:08
  • 2
    As a suggestion, build on top of another framework that already has a solid foundation. I've had good experiences doing similar things with BackgridJS: http://backgridjs.com. Columns are described as model attribute; rows are just models. The rendering of cells is wonderfully flexible. Backgrid's core components are extensible, or even replaceable. – Peter Wagener Feb 22 '15 at 20:30
  • 1
    Sounds like what you're looking for is a CompositeView that takes multiple collections. Unfortunately that's not available in the Marionette library at this time. Your HTML will not validate, but you can do what you're proposing with a LayoutView. Let me know if you need help. – seebiscuit Feb 23 '15 at 00:09
  • @Seebiscuit, I do need some help with the Layout. I am not able to get it beyond this stage. I think I am missing some critical concept here. – zaphod Feb 23 '15 at 09:30

1 Answers1

4

There are two parts to this answer. First, I recommed you use a LayoutView with CollectionViews, since you don't need a template for the collection itself (you'll still use the ItemView templates, though). Second, you'll have to let your Row view know how many check-mark columns you'll need (this will be trivial as you'll see) and we'll have to create those columns in the Row view.

Load up your LayoutView

Your FoodLayout view and template are perfect. You laid the foundation. What you need to populate it with is two CollectionView views:

FoodManager.ColItemView = Marionette.ItemView.extend({
    template: "#food-col-item",
    tagName: "th",
});

FoodManager.ColView = Marionette.CollectionView.extend({
    itemView: FoodManager.ColItemView
});

FoodManager.RowItemView = Marionette.ItemView.extend({
    template: "#food-row-item",
    tagName: "tr",
});

FoodManager.RowView = Marionette.CollectionView.extend({
    itemView: FoodManager.RowItemView
});

Note that I changed your CompositeView to CollectionView, and changed your the tagName in the ItemView to tr for the Row view. (Note: you're going to want to remove the <th> tags in #food-col-item, Backbone will generate them for you.)

Generating dynamic columns in Row view

Now here comes the fun part. We're going to use templateHelpers to make the check-mark rows in your Row views. If you'll look at the docs, templateHelpers is a hash that let's you add data to your template before rendering. That "data", or JavaScript objects, can be functions (since function are first class objects in JavaScript). So, we're going to use templateHelpers to pass the food columns we'll need check-marks for, and to put together a function that will take as a parameter the food columns, and will return with the html for those check-mark columns.

Put this templateHelpers property in your 'FoodManager.FoodLayout view:

templateHelpers: function () {
  return {
    foods: function () {
      return this.foodColumns
    },

    addColumns: function (foodcols) {
      var html = '';
       for (food in foodcols)
         html += "<td><input type="checkbox" class=" + 
                    food + "-check"></td>";

       return html;
    }
  }
} 

And your Row template will look like:

<script id="food-row-item" type="text/template">
    <td><%= row %></td><% addColumns(foods) %>
</script>

What you need to be aware of is that you need to give the FoodManager.FoodLayout the food columns you used for ColCollection, so that you can populate this.templateHelpers.foods. There are mutliple ways to get that in there. I just used this.foodColumns as dummy placeholder.

seebiscuit
  • 4,905
  • 5
  • 31
  • 47
  • Thank you so much for the detailed explanation. I could get it working. The only change I made was to pass foodColumns in rowCollectionView instead of the Layout. – zaphod Feb 24 '15 at 10:32
  • This is the working version, for reference: http://jsfiddle.net/zaphod013/mg3xcx5h – zaphod Feb 24 '15 at 10:33
  • 1
    Couple of things: 1. (minor) `serializeData` does what the `templateHelpers` attempts to do with one major difference, `this` inside `serializeData` is the view, while `this` inside templateHelpers` is the `data` mixed in with `serializeData`. If you'll look at the fiddle I removed the `food` property in `templateHelpers` and left your overriden `serializedData`. 3. (minor) I added a `tr` to your `ColView`. That replaces the bounding `div` of the view and let's Bootstrap do it's work. – seebiscuit Feb 24 '15 at 13:35
  • 1
    3. (major) Unfortunately, there's no *easy* workaround for the bounding `div` in `RowView`. You could attach the rows directly to the Region. I came up with a workaround for that, but I haven't tested it extensively. Here's a link to an Answer that implements it: http://stackoverflow.com/a/27473851/2112866. And here's a link to an updated fiddle that incorporates the first 2 items: http://jsfiddle.net/mg3xcx5h/2/ – seebiscuit Feb 24 '15 at 13:38
  • 1
    I applied 3. Please study it carefully, there are major changes, not least of which is that it uses the current Mn ver (2.4.0). This changes `itemView` to `childView`, and `Layout` to `LayoutView`. See the fiddle here: https://jsfiddle.net/Seabiscuit/azmww8dg/1/ – seebiscuit Feb 24 '15 at 16:18
  • Thank you. I was going to try out the solution suggested in http://stackoverflow.com/questions/14656068/turning-off-div-wrap-for-backbone-marionette-itemview. But I suppose this will not work since we have two regions and two divs within the layout? – zaphod Feb 25 '15 at 00:02