Bret Fisher Bret Fisher - 5 months ago 17
Javascript Question

Tracking even/odd index of Meteor Spacebars Collection (blaze)

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.

Answer

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);
  }
}
Comments