I\'m trying to achieve a \"group by\" functionality in Ember.js and its proving more difficult than I originally thought.
[{date: \'2014-01-15T16:22:16-08:00\',
Here's a Groupable implementation I've adapted a bit:
Groupable = Ember.Mixin.create
group: null
ungroupedContent: null
groupedContent: (->
model = @
groupedContent = Ember.A([])
groupCallback = @get('group')
ungroupedContent = @get('ungroupedContent')
return groupedContent unless groupCallback
return groupedContent unless ungroupedContent
ungroupedContent.forEach (item) ->
group = groupCallback.call(model, item)
return unless groupKey = group.get('key')
foundGroup = groupedContent.findProperty('group.key', groupKey)
unless foundGroup
foundGroup = groupedContent.pushObject Ember.ArrayProxy.create
group: group,
content: Ember.A([])
foundGroup.get('content').pushObject(item)
groupedContent
).property('group', 'ungroupedContent.@each')
Controller usage:
ActivitiesController = Ember.ArrayController.extend Groupable,
ungroupedContentBinding: 'content' # tell Groupable where your default content is
# the callback that will be called for every
# item in your content array -
# just return the same 'key' to put it in the same group
group: (activity) ->
Ember.Object.create
key: moment.utc(activity.get('date')).format('YYYY-MM-DD') # using momentjs to pluck the day from the date
description: 'some string describing this group (if you want)'
Template usage:
{{#each groupedContent}}
{{group.key}} - {{group.description}}
{{#each content}}
Here's the really bad thing that happened: {{message}}
{{/each}}
{{/each}}
Essentially, all that the Groupable mixin does is loop through your normal content ArrayProxy and calls a function to determine what group it belongs to. If that group object doesn't already exist (i.e. no matching group.key
was found), it creates it before adding the current content item to the group's content.
So what you end up with in your template is a new array (ArrayProxy
) of objects (Ember.Object
), with each object having a group
property (which must have at least a key
property to identify it) and a content
property (which contains the content belonging to that group).
You can use Ember.computed.groupBy
like this:
((Ember) ->
# Example:
#
# productsByCategory: Em.computed.groupBy 'products', 'category'
#
# each view.productsByCategory
# category.name
# each content
# content.name
#
Ember.computed.groupBy = (collection, groupBy) ->
dependentKey = "#{collection}.@each.#{groupBy}"
Ember.computed dependentKey, ->
result = []
@get(collection).forEach (item) ->
unless result.findBy groupBy, item.get(groupBy)
data = content: []
data[groupBy] = item.get(groupBy)
result.pushObject Ember.Object.create(data)
result.findBy(groupBy, item.get(groupBy)).get('content').pushObject item
result
) Ember
Here is an implementation, made available on Array
objects:
var groupByMethod = Ember.Mixin.create({
groupBy: function(key) {
var result = [];
var object, value;
this.forEach(function(item) {
value = item.get ? item.get(key) : item[key];
object = result.findProperty('value', value);
if (!object) {
object = {
value: value,
items: []
};
result.push(object);
}
return object.items.push(item);
});
return result.getEach('items');
}
});
groupByMethod.apply(Array.prototype);
> var posts = [{id: 1, author: 'John'}, {id:2, author: 'Paul'}, {id:4, author: 'Paul'}, {id:5, authour: 'Tom'}];
> posts.groupBy('author')