mixin Enumerable
Description
Enumerable
provides a large set of useful methods for enumerations —
objects that act as collections of values. It is a cornerstone of
Prototype.
Enumerable
is a mixin: a set of methods intended not for standalone
use, but for incorporation into other objects.
Prototype mixes Enumerable
into several classes. The most visible cases
are Array
and Hash
, but you'll find it in less obvious spots as
well, such as in ObjectRange
and various DOM- or Ajax-related objects.
The context
parameter
Every method of Enumerable
that takes an iterator also takes the "context
object" as the next (optional) parameter. The context object is what the
iterator will be bound to — what the keyword this
will refer to inside
the iterator.
var myObject = {};
['foo', 'bar', 'baz'].each(function(name, index) {
this[name] = index;
}, myObject); // we have specified the context
myObject;
// -> { foo: 0, bar: 1, baz: 2}
If there is no context
argument, the iterator function will execute in
the scope from which the Enumerable
method itself was called.
Flow control
You might find yourself missing the break
and continue
keywords that
are available in ordinary for
loops. If you need to break out of an
enumeration before it's done, you can throw a special object named
$break
:
var myObject = {};
['foo', 'bar', 'baz', 'thud'].each( function(name, index) {
if (name === 'baz') throw $break;
myObject[name] = index;
});
myObject;
// -> { foo: 0, bar: 1 }
Though we're technically throwing an exception, the each
method knows
to catch a thrown $break
object and treat it as a command to stop
iterating. (Any exception thrown within an iterator will stop
iteration, but only $break
will be caught and suppressed.)
If you need continue
-like behavior, you can simply return early from
your iterator:
var myObject = {};
['foo', 'bar', 'baz', 'thud'].each( function(name, index) {
if (name === 'baz') return;
myObject[name] = index;
});
myObject;
// -> { foo: 0, bar: 1, thud: 3 }
Mixing Enumerable
into your own objects
So, let's say you've created your very own collection-like object (say,
some sort of Set, or perhaps something that dynamically fetches data
ranges from the server side, lazy-loading style). You want to be able to
mix Enumerable
in (and we commend you for it). How do you go about this?
The Enumerable module basically makes only one requirement on your object:
it must provide a method named _each
(note the leading underscore) that
will accept a function as its unique argument, and will contain the actual
"raw iteration" algorithm, invoking its argument with each element in turn.
As detailed in the documentation for Enumerable#each
, Enumerable
provides all the extra layers (handling iteration short-circuits, passing
numeric indices, etc.). You just need to implement the actual iteration,
as fits your internal structure.
If you're still confused, just have a look at the Prototype source code for
Array
, Hash
, or ObjectRange
. They all begin with their own
_each
method, which should help you grasp the idea.
Once you're done with this, you just need to mix Enumerable
in, which
you'll usually do before defining your methods, so as to make sure whatever
overrides you provide for Enumerable
methods will indeed prevail. In
short, your code will probably end up looking like this:
var YourObject = Class.create(Enumerable, {
initialize: function() { // with whatever constructor arguments you need
// Your construction code
},
_each: function(iterator) {
// Your iteration code, invoking iterator at every turn
},
// Your other methods here, including Enumerable overrides
});
Then, obviously, your object can be used like this:
var obj = new YourObject();
// Populate the collection somehow
obj.pluck('somePropName');
obj.invoke('someMethodName');
obj.size();
// etc.