user533844 user533844 - 1 month ago 7
Javascript Question

Handlebars.js custom function sort

I am using Handlebars to display comments.

{{#each COMMENTS}}
<div class="heightfix">
<div class="commentIcon"></div>&nbsp;&nbsp;
<label id="commentDate1" class="bold">{{COMMENTON}}</label>:&nbsp;
<label id="commentCreator1" class="bold">{{COMMENTBY_FIRSTNAME}} {{COMMENTBY_LASTNAME}}</label>&nbsp;&nbsp;
<label class="commentContent" id="commenttext_{{ID}}">{{COMMENTTEXT}}</label>
</div>
{{/each}}


In those COMMENTS, I have INDEX. I would like to display the comments as per their index.
Comment 0
Comment 1
Comment 2
Comment 3 .....

How do I achieve this using custom functions ?
Thanks

Answer

Method 1: Custom Accessor

If you have control over the context (and you probably do), you can implement this with property accessors. Let's say your context contains the comments in jumbled order:

var context = {
    comments: [
        { idx:6, text:'violet' },
        { idx:1, text:'orange' },
        { idx:0, text:'red' },
        { idx:5, text:'indigo' },
        { idx:3, text:'green' },
        { idx:2, text:'yellow' },
        { idx:4, text:'blue' },
    ],
};

You could just sort the comments array in place before rendering your template (note that for numeric values, you can just subtract them to get natural ordering):

context.comments.sort( function(a,b) { return a.idx - b.idx; } );

The problem with that is that will modify the context object, which you may not want to do. A solution is to provide a custom accessor that will return the comments sorted by some custom property. Here's how we can do that:

Object.defineProperty( context, 'commentsByIdx', {
    get: function() {
        // note that we call concat() to create a copy of
        // the array; otherwise we are modifying the
        // the original array, which is a side effect
        return this.comments.concat()
            .sort( function(a,b) { return a.idx - b.idx } );
    }
});

You can see this solution in action here: http://jsfiddle.net/Jammerwoch/ShZLY/

Method 2: Handlebars Block Helper

The other approach is to implement a Handlebars block helper. This approach will work better if you don't have much control over the context (or don't want to contaminate it with custom properties). Simply register a Handlebars helper:

Handlebars.registerHelper('eachByIdx', function(context,options){
    var output = '';
    var contextSorted = context.concat()
        .sort( function(a,b) { return a.idx - b.idx } );
    for(var i=0, j=contextSorted.length; i<j; i++) {
        output += options.fn(contextSorted[i]);
    }
    return output;
});

And call it like so in your template:

{{#eachByIdx comments}}
    <li>{{text}}</li>
{{/eachByIdx}}

See it in action here: http://jsfiddle.net/Jammerwoch/aeCYg/1/

All in all, I find the first method preferable, as it creates a new view of your data that's potentially useful outside of Handlebars. Furthermore, to the casual reader of Handlebars code, it will make more sense.

Comments