2

Using Meteor 0.8.x

I'm trying to fill a template with two columns of documents in a collection. Half in one column, half in the other. Best I could figure was to run through the collection twice, once for each side and use a index or counter to track even/odd. Spacebars doesn't have @index or @even @odd. There are multiple discussions about this in stackoverflow and ggroup but I'm not sure how to do that correctly in my own helper.

Tried this template:

<div class="row">
    <article>
        {{#each post}}
            {{#if oddCounter}}
               {{> templatePost}}
            {{/if}}
        {{/each}}
    </article>
    <article>
       {{#each post}}
           {{#if evenCounter}}
               {{> templatePost}}
           {{/if}}
       {{/each}}
    </article>
</div>

with these helpers:

var evenPost = 0;
var oddPost = 0;

Template.dashboard.helpers({
    evenCounter: function () {
        return (evenPost++ % 2 === 0);
    },
    oddCounter: function () {
        return !(oddPost++ % 2 === 0);
    },
    posts: function () {
        return Posts.find();
    }
});

For one, this seems crude, and does work initially. But it actually has a flaw where anytime a document is changed and the template is redrawn (templatePost) it iterates the oddCounter/evenCounter variables and moves the item to the other column. Not sure how to solve this.

Is my method of counting for each post totally flawed?

Can you refactor this to prevent Meteor from incrementing on redraw?

Maybe use a custom .map and cursor function to add index to the collection on client? (saw this in Discover Meteor book but couldn't get it working right).

Thanks!

Update 1: Mr D's solution works, but I was able to get the Discover Meteor example from Animations chapter working and stuck with it.

Template.dashboard.helpers({
    posts: function () {
        var index = 0;
        var posts = Posts.find();
        return posts.map(function(post, index, cursor) {
            post._index = index++;
            return post;
        });
    },
    even: function () {
        return (this._index % 2) === 0;
    }
});

Update 2: For a few less lines of code, remove the odd counter and use {{#unless even}} in your template to track odd documents.

Bret Fisher
  • 8,164
  • 2
  • 31
  • 36
  • 3
    This seems like a problem that is **much more easily** tacked with CSS, not code. Just throw all the items in a container and make sure they fit two wide. It even has `:even` and `:odd` pseudo-selectors for styling the elements in your two columns. – Andrew Mao Jul 16 '14 at 05:45

2 Answers2

2

Due to the reactive nature of meteor your method won't work as when the data changes, the counters will not match what is being shown.

One way to make your method work would to be to add a Deps.dependancy to recompute the values every time a change happens, but that seems unnecessarily expensive, and adding an index to each document is better.

This method will be pretty inefficient, but if you have a low number of posts it should work fine.

 <div class="row">
        <article>
            {{#each post}}
                {{#if odd}}
                   {{> templatePost}}
                {{/if}}
            {{/each}}
        </article>
        <article>
           {{#each post}}
               {{#if even}}
                   {{> templatePost}}
               {{/if}}
           {{/each}}
        </article>
    </div>

Template.dashboard.helpers = {
  post: function() {
    var posts = [];
    var _i = 0;
    Posts.find().forEach(function(p) {
      p.position = _i;
      _i++;
      posts.push(p);
    });
    return posts;
  },
  odd: function() {
    return !(this.position % 2 === 0);
  },
  even: function() {
    return (this.position % 2 === 0);
  }
}
1321941
  • 2,139
  • 5
  • 26
  • 49
  • This works! Note that I actually got the https://www.discovermeteor.com example working (from the Book chapter on Animations) but Mr D's is fine too. – Bret Fisher Jul 14 '14 at 16:46
1

You can consider upgrading to Meteor 1.2 or 1.3 which finally has row index in templates:

{{@index}} - index of the row, starting from 0

And here is a helper to highlight even rows:

<div class="{{#if even @index}}evenClass{{/if}}">
  I'm a row.
</div>
Template.template_name.helpers({
    even(value) {
        return (value % 2) === 0;
    },
});

Also consider checking the post How can I get the index of an array in a Meteor template each loop?

Jirik
  • 1,435
  • 11
  • 18
romaroma
  • 658
  • 8
  • 13