SproutCore Guides

These guides are designed to help you write and perfect your code.

Enumerables - The SproutCore Enumerable API

This guide covers how to work with enumerable SproutCore objects, like Arrays, Sets, ArrayControllers, and RecordArrays. When you are done with this guide, you should be able to:

  • Identify the SproutCore enumerable objects.
  • Fluently work with enumerable objects using the Enumerable interface.

1 - What Are Enumerables?

In SproutCore, an Enumerable is any object that contains a number of child objects, and which allows you to work with those children using the Enumerable interface. The most basic Enumerable is the built-in JavaScript Array.

For instance, all Enumerables support the standard forEach method:

[1,2,3].forEach(function(item) { console.log(item); });

In general, Enumerable methods, like forEach, take an optional second parameter, which will become the value of this in the callback function:

var array = [1,2,3]; array.forEach(function(item) { console.log(item, this.indexOf(item)); }, array)

Among other reasons, you will find this useful when using another Enumerable method as a callback to forEach:

var array = [1,2,3]; array.forEach(array.removeObject, array);

This second parameter helps work around a limitation of JavaScript which sets the value of this to window in methods used this way.

2 - Enumerables in SproutCore

In general, SproutCore objects that represent lists implement the Enumerable interface. Some examples:

  • Array: SproutCore extends the native JavaScript Array with the Enumerable interface.
  • SC.ArrayController: A construct that wraps a native Array and adds additional functionality for the view layer.
  • SC.SparseArray: An Array-like object that will incrementally load in ranges as they are needed by the view layer.
  • SC.RecordArray: An Array-like object that represents a list of Record objects. RecordArray's are "live", and the Data Store can update them when new data comes in from the backend.
  • SC.Set: An object that can quickly answer whether it includes an object.

3 - The Enumerable Interface

3.1 - Parameters

The callbacks to Enumerable methods take three arguments:

  • item: the item for the current iteration.
  • index: an Integer, counting up from 0.
  • self: the Enumerable itself.

3.2 - Enumeration

To enumerate all the values of an enumerable object, use the forEach method:

enumerable.forEach(function(item, index, self) { console.log(item); });

To invoke some method on each element of an enumerable object, use the invoke method:

Person = SC.Object.extend({ sayHello: function() { console.log("Hello from " + this.get('name')); } }); var people = [ Person.create({ name: "Juan" }), Person.create({ name: "Charles" }), Person.create({ name: "Majd" }) ] people.invoke('sayHello'); // Hello from Juan // Hello from Charles // Hello from Majd

3.3 - First and Last

You can get the first or last object from an Enumerable by getting firstObject or lastObject.

[1,2,3].get('firstObject') // 1 [1,2,3].get('lastObject') // 3

3.4 - Converting to Array

This one is simple. To convert an Enumerable into an Array, simply call its toArray method.

3.5 - Transforming

You can transform an Enumerable into a derived Array by using the map method:

['Goodbye', 'cruel', 'world'].map(function(item, index, self) { return item + "!"; }); // returns ["Goodbye!", "cruel!", "world!"]

3.6 - Setting and Getting on Each Object

A very common use of forEach and map is to get (or set) a property on each element. You can use the getEach and setEach methods to accomplish these goals.

var arr = [SC.Object.create(), SC.Object.create()]; // we now have an Array containing two SC.Objects arr.setEach('name', 'unknown'); arr.getEach('name') // ['unknown', 'unknown']

3.7 - Filtering

Another common task to perform on an Enumerable is to take the Enumerable as input, and return an Array after sorting or filtering it based on some criteria.

For arbitrary filtering, use the (you guessed it) filter method. The filter method expects the callback to return true if SproutCore should include it in the final Array, and false or undefined if SproutCore should not.

var arr = [1,2,3,4,5]; arr.filter(function(item, index, self) { if (item < 4) { return true; } // Note, this could also be written as: return item < 4; }) // returns [1,2,3]

When working with a collection of SproutCore objects, you will often want to filter a set of objects based upon the value of some property. The filterProperty method provides a shortcut.

Todo = SC.Object.extend({ title: null, isDone: false }); todos = [ Todo.create({ title: 'Write code', isDone: true }), Todo.create({ title: 'Go to sleep' }) ]; todos.filterProperty('isDone', true); // returns an Array containing just the first item

If you want to return just the first matched value, rather than an Array containing all of the matched values, you can use find and findProperty, which work just like filter and filterProperty, but return only one item.

3.8 - Sorting

You can sort an Enumerable based on the value of some property or list of properties using sortProperty. If you pass in multiple properties, SproutCore will sort items with the same value for the first property by the value of the second parameter, and so on.

var todos = [ Todo.create({ title: 'Write code', isDone: true }), Todo.create({ title: 'Go to sleep' }), Todo.create({ title: 'Eat lunch', isDone: true }) ]; todos.sortProperty('isDone', 'title'); // returns an Array containing // * Go to sleep // * Eat lunch // * Write code

Internally, the sortProperty method uses SC.compare, which uses SproutCore's comparable semantics. You can override the default comparison behavior for a custom object by using the SC.Comparable mixin.

3.9 - Aggregate Information (All or Any)

If you want to find out whether every item in an Enumerable matches some condition, you can use the every method:

Person = SC.Object.extend({ name: null, isHappy: false }); var people = [ Person.create({ name: 'Yehuda', isHappy: true }), Person.create({ name: 'Majd', isHappy: false }) ]; people.every(function(person, index, self) { if(person.get('isHappy')) { return true; } }); // returns false

If you want to find out whether at least one item in an Enumerable matches some conditions, you can use the some method:

people.some(function(person, index, self) { if(person.get('isHappy')) { return true; } }); // returns true

Just like the filtering methods, the every and some methods have analogous everyProperty and someProperty methods.

people.everyProperty('isHappy', true) // false people.someProperty('isHappy', true) // true

4 - Changelog

  • May 3, 2011: initial version by Yehuda Katz
  • August 14, 2013: converted to Markdown format for DocPad guides by Topher Fangio
  • August 14, 2013: minor formatting and example updates by Topher Fangio