Search code examples
javascriptember.jsember-cli

Split and group a collection of items in Ember


I'm trying to take a collection of records in Ember and split them into groups of some number, say 2.

So for example, with something like

{{#each node in model}}
  <span>node.name</span>
{{/each}}

I get <span>thing</span><span>other thing</span><span>some thing</span><span>one more thing</span>

I want to be able to pass node to something and wrap every 2 nodes with a div something like <div><span>thing</span><span>other thing</span></div><div><span>some thing</span><span>one more thing</span></div>

In Ember 2.0 where most everything should be a component, where is the best place to handle this logic. Should it be a component or controller?


Solution

  • Given the principle that things related to display, or preparations therefor, belong in the component, I would prefer the component. You could do it like so:

    partitions: computedPartition('model', 2)
    

    Then in your template

    {{#each partition in partitions}}
      <div>
        {{#each node in partition}}
          {{node.name}}
        {{/each}}
      </div>
    {{/each}}
    

    Now it remains to write computedPartition, which is an Ember computed property:

    function computedPartition(dependentKey, size) {
      return Ember.computed(dependentKey + ".@each", function() {
        return partition(this.get(dependentKey), size);
      });
    }
    

    There are different algorithms for partitioning. See this question. Here's a short recursive one:

    function partition(array, n) {
      array = array.slice();
      return function _partition() {
        return array.length ? [array.splice(0, n)].concat(_partition()) : [];
      }();
    }
    

    Going deeper

    We can simplify (?) the above by introducing a higher-level computed property called computedArrayInvoke, which invokes a specified function on the array-valued property with the specified key, along with additional arguments:

    function computedArrayInvoke(fn, dependentKey, ...args) {
      return Ember.computed(dependentKey + ".@each", function() {
        return fn(this.get(dependentKey), ...args);
      });
    }
    

    Now we can write

    partitions: computedArrayInvoke(partition, 'model', 2)