• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Runtime-Using SCEnumerable

Page history last edited by Tom Dale 14 years, 4 months ago

The SC.Enumerable mixin describes a generic interface for accessing any kind of collection, ordered or unordered.  Unless you are working with a private array, you should always use SC.Enumerable (and SC.Array) methods to work with collections of objects (such as Arrays).  SC.Enumerable methods are almost as fast as native functions provided by Array but they can also work with a broader range of objects and work properly with key value observing.

 

This section will explain how to work with objects that use the SC.Enumerable API.

 

Length Property 

 

Every enumerable implements at least one primitive property called length.  This property contains the number of items currently in the enumerable.  This is the same as the length property defined on the built-in Array object.  

 

Because SC.Enumerable objects are observable, the length property may be computed.  Therefore you must always use get('length') to retrieve the current number of items in any SC.Enumerable object.  If you simply access the length property directly you may retrieve a function used to compute the length instead of a number itself.  Using get() will avoid this problem:

 

// WRONG!!!! - may not retrieve the actual length

var len = anEnumerable.length;

// RIGHT - always retrieves the actual length

var len = anEnumerable.get('length');

 

Note that length is also observable.  You can connect bindings to length to keep track of the number of items in the enumerable and so on.  Any observers or bindings connected to length will fire only when the length itself changes.  This means if you replace some items in an enumerable (leaving the overall length the same), your observer may not fire at all.  

 

Only observe the length property when you actually care about the number of items in the enumerable.  If you want to instead simply be notified whenever the items in an enumerable change, observe the enumerable property instead.

 

Observing Enumerable

 

Since length describes the actual number of items in the enumerable, you need some way to also be notified whenever the enumerable changes, even if the total number of items remains the same.  For this purpose, SC.Enumerable defines a special property called the enumerable property.  The key itself is simply  a pair of square brackets ('[]').  Enumerable is a computed property that simply returns the enumerable itself.  The value of enumerable is never interesting.  However, any observers on this property are triggered whenever the enumerable contents changes, which is interesting:

 

var ary = [];

// this observer will fire anytime an item is added to or removed from the array

ary.addObserver('[]', function() {

 console.log('ary contents changed!');

});

 

Iterating

 

Enumerables may be ordered or unordered.  An ordered enumerable is like an Array - objects appear in a specific order, which you can usually access by a specific value.  Unordered enumerables do not guarantee that your objects will be stored in any particular order.  However they will often be faster at other operations such as adding/remove items to testing for membership.

 

Since enumerables may be unordered, you cannot access a generic enumerable's content by a specific index.  Instead, you must step through the contents when you want to work with using an iterator.  An iterator is simply a method that will invoke a callback you provide on each of the items in the enumerable.  Depending on the type of iterator method, the return value of your callback may determine how the iterator interacts with the results.

 

Basic Iterating: forEach()

 

The most common way you iterate through an enumerable is with the forEach() method:

 

var set = SC.Set.create(1,2,3);

set.forEach(function(item) {

  console.log(item);

}, this);

> 1

> 2

> 3

 

forEach() as defined on SC.Enumerable works exactly like the forEach() method defined in JavaScript 1.6.  The first parameter you pass should be a callback function that will be invoked once for each item in the enumerable.  An optional second parameter is a target object that will become the value of "this" when the callback is invoked.

 

IMPORTANT: Whenever you want to invoke a callback in forEach() or any of the other iterators defined here, you should generally pass "this" as the second parameter to make your callback work like other code in the same method.  You do not need to .bind() or otherwise prepare your function callback before passing it to forEach().

 

The callback you pass into forEach() should have the following signature:

 

function callback(item, index, enumerable) { ... }

 

The following parameters are passed to the callback.   If you don't care about some of the parameters, you can define your function to receive fewer parameters instead:

 

  • item: the current item in the iteration
  • index: the current index in the iteration.  For unordered enumerables, this is just a number that will increase each time your callback is invoked.  It has no meaning to the actual internal structure of the enumerable.
  • enumerable: the enumerable you are iterating over

 

Note that modifying an enumerable from within an iterator will have an unknown effect on how the rest of your methods will be called.  You should invoke this with caution.

 

Getting and Setting Properties: getEach(), setEach()

 

If you are working with a collection of objects and you need to simply get or set a property value on the objects, you can use the getEach() and setEach() iterators instead of forEach().  Unlike forEach(), these iterators do not take a callback.  Instead you pass a property name (and a new value for setEach()).  Because they do not require executing a callback, these iterators are often faster and easier:

 

// get firstName for all contacts

var set = SC.Set.create(  

  { firstName: "John", lastName: "Foo" }

  { firstName: "Jane", lastName: "Bar" }

);

 

set.getEach('firstName')

> ['John', 'Jane']

 

set.setEach('firstName', 'JACK');

> Set( { firstName: "JACK", lastName: "Foo" },

       { firstName: "JACK", lastName" "Bar" } )

 

Selecting Items: filter(), filterProperty()

 

Sometimes you have a collection of objects and you simply want to select a subset of those objects based on some criteria.  For this purpose you can use the filter() iterator.  This iterator will invoke your callback on each item in the enumerable and then return a new enumerable that contains only the items for which your callback returned YES:

 

// get only items larger than 10

var set = SC.Set.create(1, 5, 10, 15, 20);

set.filter(function(item) {

 return item > 10;

}, this);

> [15, 20]

 

Note that even though the default version of filter() returns an Array, not all implementations of filter() may return an Array.  You are only guaranteed that the returned item will implement SC.Enumerable.  Always use the SC.Enumerable interface to work with returned results.

 

If you are working with a set of objects can you need to filter based on some property on those objects, it is often easier and faster to use the filterProperty() iterator instead of filter().  Instead of passing a callback, filterProperty() simply takes a property name.  The return value is a new enumerable with only those objects for which the property name resolves to a truthy value:

 

// get only active items

var set = SC.Set.create({ value: 1, isActive: NO },  { value 2, isActive: YES });

set.filterProperty('isActive');

> [ { value: 2,  isActive: YES } ]

 

You can also pass an optional value to test against instead of a simple truth test:

 

// get items with a value of 1

var set = SC.Set.create({ value: 1, isActive: NO }, { value: 2, isActive: YES });

set.filterProperty('value', 1);

> [ { value: 1, isActive: YES } ]

 

Mapping Iterators: map()

 

Another common reason you might iterate over an enumerable set is to extract a value from the items in the set.  You can use the map() iterator to help you do this.  map() will invoke your callback on each item in the enumerable and then return a new enumerable with the return value from your callback for each item in the new set:

 

// multiple each item in the set by 2:

var set = SC.Set.create(1, 5, 10, 20);

set.map(function(item) {

 return item * 2;

}, this);

> [2, 10, 20, 40]

 

Invoking Methods: invoke()

 

If you simply need to invoke a method on each object in a set, you can use the helper method invoke().  This method takes a property name which must map to a function value on each item in the enumerable.  It will then invoke that method with any additional arguments you pass:

 

// call send(body) on each item in the set

var requests = <SC.Set of Request objects>

requests.invoke('send', 'body');

 

Searching 

 

Another common task with enumerables is searching for items.  SC.Enumerable defines several common ways for you to search.

 

Basic Searching: find(), findProperty()

 

The find() method will invoke a callback on each item in the enumerable until the callback return YES.  It will return the item for which the callback was YES or null if not matching item was found.

 

// finds an item that is > 10

var set = SC.Set.create(1, 5, 10, 20);

set.find(function(item) {

 return item > 10;

}, this);

> 20

 

If you simply need to find an item with a property matching a certain value, you can use the shortcut findProperty().  findProperty() takes a property name and a target value.  Since it does not require a callback, findProperty() is often faster and simpler than a regular find():

 

// find the first contact with the firstName 'John'

var set = <Set of Contact objects>

set.findProperty('firstName', 'John'); 

> Contact({ firstName: John, lastName: Doe })

 

Testing: some(), every(), someProperty(), everyProperty()

 

If you don't care so much about the value of an item itself but you just want to know if an item in your enumerable matches a given condition, then use the some() iterator.  This will invoke your callback on each item in the enumerable until your callback returns YES.  It returns YES if your callback returned YES and NO otherwise.

 

// returns YES if an item is > 10

var set = SC.Set.create(1, 5, 10, 20);

set.some(function(item) {

 return item > 10;

});

> true

 

If you want to know that every item in the enumerable matches a certain condition, use the every() iterator instead.  This returns YES only if your callback returns YES for every item in the enumerable:

 

var set = SC.Set.create(1, 5, 10, 20);

// returns YES if every item > 10 [it's not]

set.every(function(item) {

  return item > 10;

}, this);

> false;

// returns YES if every item > 0 [it is]

set.every(function(item) {

  return item > 0 ;

}, this);

> true

 

Like their other iterators, some() and every() have companion methods, someProperty() and everyProperty() that take a property name and value.  These are often faster than some() and every() because they do not invoke a callback directly.

 

Testing For SC.Enumerable

 

If you aren't sure a given object implements SC.Enumerable, you can easily test for it.  Just verify that isEnumerable is YES:

 

// returns YES

var set = SC.Set.create();

set.get('isEnumerable');

> true

 

Note that Array has SC.Enumerable added to it automatically.  All Arrays are enumerable.

 

Reduced Properties 

 

Reduced properties are special properties that compute summary information about an enumerable automatically.  Learn more about them in the Reducers section.

 

Moving On

 

Now that you know how to use generic Enumerables:

 

Learn how to work with SC.Array »

or back to Enumerables Home »

 

 

 

 

Comments (0)

You don't have permission to comment on this page.