Handlebars Block Helper : each with sort

coffeescript, handlebars.js, json

Solution

The easiest thing to do would be to sort the data before it gets to Handlebars, then you can use `{{#each ...}}` as usual and no helpers are needed. This sort of approach is quite common with Handlebars, the template is often split into two pieces: a (Java|Coffee)Script piece for data mangling/rearrangement and the template proper.

As an aside, you'll want to adjust your comparator function to behave property. From the fine manual:

If `compareFunction` is supplied, the array elements are sorted according to the return value of the compare function. If `a` and `b` are two elements being compared, then:

- If `compareFunction(a, b)` is less than 0, sort `a` to a lower index than `b`, i.e. a comes first.

- If `compareFunction(a, b)` returns 0, leave `a` and `b` unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behaviour, and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this.

- If `compareFunction(a, b)` is greater than 0, sort `b` to a lower index than `a`.

So you want to return `0` if `a.sort_index` and `b.sort_index` are the same, something more like this:

myArray.sort (a,b)->
  a = +a.sort_index
  b = +b.sort_index
  return  1 if a > b
  return  0 if a == b
  return -1 if a < b

If you must do the sorting inside the template then you'd need to add a custom `each_with_sort` helper to do both the sorting and the iteration, something like this:

# If you're always sorting by sort_index then you don't need the key argument
# and you might want to put the '+'-casting back in.
Handlebars.registerHelper('each_with_sort', (array, key, opts) ->
    data  = Handlebars.createFrame(opts.data) if(opts.data)
    array = array.sort (a, b) ->
        a = a[key]
        b = b[key]
        return  1 if a > b
        return  0 if a == b
        return -1 if a < b
    s = ''
    for e, i in array
        data.index = i if(data) # Support the usual @index.
        s += opts.fn(e, data: data)
    s
)

and your template would be like this:

{{#each_with_sort array "sort_index"}}
    ...
{{/each_with_sort}}

Demo: http://jsfiddle.net/ambiguous/zusq2tt4/

Problem

I have an array of json objects which I output using a Handlebars template; I am currently doing using {{#each object}}...{{/each}}. I now need to sort the objects by one of the object's properties, which again is no problem using a handlebars helper & coffeescript, however, I have a problem in my template in that I cannot work out how to iterate over the sorted array using each. My research so far indicates that I probably need to write a custom Handlebars helper which will, in effect, be: ``` {{#each_with_sort array}} ``` My existing sort helper is like this ``` Handlebars.registerHelper sort_me => myArray.sort (a,b)-> return if +a.sort_index >= +b.sort_index then 1 else -1 ``` but, I am struggling to be able to use the sorted array in the template - for example, it is not as simple as ``` {{#each sort_me(myArray)}} ``` The data is coming from a third party API, so I have to perform sorting in handlebars/coffeescript.

Original source