Iterators (library)

Categories:Flash

Overview

Summary

The iterators library provides a standardized and object-oriented approach to iterating over-and-into Documents, Timelines, Layers, (Key)Frames, and Elements.

Note: to loop over and modify only Stage elements or Library items see the ElementCollection and ItemCollection classes.

Contents

Usage

Instead of writing and managing potentially fiddly nested loop code to handle DOM traversal, you now leave the looping logic to the Iterators API, and delegate the processing functionality to your own custom callback functions:

function processElement(element, index, elements, context)
{
    // your processing code here
}

Each of the functions in the API accepts multiple callbacks, progressively down the chain, until you reach Element level:

Iterators.items(context, itemCallback, layerCallback, frameCallback, elementCallback);

You can start processing at any level (using the relevant method) and finish at any (lower) level, depending on whether a callback for a level has been supplied. You can choose to skip intermediate levels, for example, you can provide callbacks for Layers and Elements, but choose to skip processing Frames (although they will be iterated over to get to Elements).

Note that many of the Iterator methods accept a Context object as their first parameter, as a reference from which to start iteration from. See the Context API for more information.

Concept

To demonstrate the differences between the two approaches in code, here is a typical JSFL Layers > Frames > Elements loop:

var timeline = fl.getDocumentDOM().getTimeline();
for(var i = 0; i < timeline.layers.length; i++)
{
    var layer = timeline.layers[i];
    for(var j = 0; j < layer.frames.length; j++)
    {
        var frame = layer.frames[j];
        if(j == frame.startFrame)
        {
            var elements = frame.elements;
            for(var k = 0; k < elements.length; k++)
            {
                var element = elements[k];
                // do something with element
            }
        }
    }
}

This kind of code is probably familiar to most Flash developers, and although it looks pretty sstandard; it's not particularly flexible, and is impossible to reuse in an object oriented manner.

Using the Iterators API however, we delegate the drill down through the DOM to the library, whilst we handle only the processing, with a simple callback:

function processElement(element, index, elements, context)
{
    trace(element.name);
}

Iterators.layers(true, null, null, processElement);
green_star
green_star
green_star
grey_oval
red_square
purple_oval

Processing multiple levels

In the previous example, only the Element elements were processed, using a single callback. Iterators gives you the option to process some or all of the elements in a chain, iterating down to the last element-type specified. To process an element at any level, you simply supply a callback to the relevant method parameter:

function processLayer(layer, index, layers, context)
{
    trace('> ' + layer.name);
}

function processFrame(frame, index, frames, context)
{
    trace('    > ' + frame.startFrame);
}

function processElement(element, index, elements, context)
{
    trace('        > ' + element.name);
}

Iterators.layers(Context.create(), processLayer, processFrame, processElement);
>actions
    > 0
>Layer 4
    > 0
        "green_star"
    > 9
        "green_star"
    > 14
        "green_star"
>Layer 3
    > 0
    > 3
        "grey_oval"
>Layer 2
    > 0
        "red_square"
>Layer 1
    > 0
    > 5
        "purple_oval"

Halting or skipping processing of levels / branches

There is one last bit of functionality that the Iterators API supports, and that's some basic influence over the internal loop logic of the Iterators looping methods.

By returning true or false values (rather than no return value) from the callback functions, you can stop or skip further processing:

  • Return a Boolean true to cancel all loop iteration, right back up to the top of the chain - for example when you've found an item you were using iteration to locate
  • Return a Boolean false to skip the current branch of iteration - for example you want to skip the current layer (and subsequently its frames and elements)

The following example illustrates this in code, iterating through all Layers, Frames and Elements, attempting to find an Symbol Item called 'red_square', but skipping guide layers as it processes:

function processLayer(layer, index, layers, context)
{
    if(layer.layerType == 'guide')
    {
        return false;
    }
}

function processElement(element, index, elements, context)
{
    if(element.name == 'red_square')
    {
        $selection = element;
        return true;
    }
}

var state = Iterators.layers(Context.create(), processLayer, null, processElement);
if(state)
{
	alert($selection[0].name + ' selected')
}

If you run this code and have a Symbol Item called 'red_square' in a frame on your timeline you should get an alert box, and the item should be selected.

API

documents(documents, documentCallback, itemCallback, layerCallback, frameCallback, elementCallback)

Iterates through Documents, and optionally Items, Layers, Frames and Elements, processing each one with a callback if supplied

Parameters:

  • documents Boolean Pass true to use all documents
  • documents null Pass null to use all documents
  • documents Array An optional Array of Document objects
  • itemCallback Function An optional callback of the format function(item, index, items, context)
  • layerCallback Function An optional callback of the format function(layer, index, layers, context)
  • frameCallback Function An optional callback of the format function(frame, index, frames, context)
  • elementCallback Function An optional callback of the format function(element, index, elements, context)

Returns:

  •   Boolean true as soon as the callback returns true, if not false

The following example iterates over all open documents, then uses the ItemSelector and ItemCollection classes to find a symbol in the library called 'star':

function documentCallback(document, index, documents, context)
{
    var collection = $$('star');
    if(collection.elements.length)
    {
        fl.setActiveWindow(document);
        collection.reveal().select();
        trace('Found in ' + document.name);
        return true; // note that true is returned to halt the Iterator processing!
    }
    trace('Not found in ' + document.name);
}
Iterators.documents(true, documentCallback);
Not found in snippets 03.fla
Not found in library items.fla
Found in frames and shapes.fla

Note that a Document does not need to have focus in order to be iterated over, or have its elements iterated over.

This code could just as easily iterate down further, by adding more callbacks to the initial method call, or could be abstracted into a global search function, such as:

findItemInOpenDocuments('star');

items(context, itemCallback, layerCallback, frameCallback, elementCallback)

Iterates through Symbol or Library Items, and optionally Layers, Frames and Elements, processing each one with a callback if supplied

Parameters:

  • context Array An Array of Symbol Items or Instances
  • context Context A Context object, with a valid dom reference
  • context Boolean Pass null to use all items
  • context null Pass null to use all items
  • itemCallback Function An optional callback of the format function(item, index, items, context)
  • layerCallback Function An optional callback of the format function(layer, index, layers, context)
  • frameCallback Function An optional callback of the format function(frame, index, frames, context)
  • elementCallback Function An optional callback of the format function(element, index, elements, context)

Returns:

  •   Boolean true as soon as the callback returns true, if not false

The following example iterates over all items and their layers, locking any mask, masked or guide layers it finds:

function itemCallback(item, index, items, context)
{
	trace('Updating:' + item.name)
}

function layerCallback(layer, index, layers, context)
{
	if(/^(mask|masked|guide)$/.test(layer.layerType))
	{
		layer.locked = true;
	}
	context.goto(); // update layer display by updating the playhead
}

Iterators.items(true, itemCallback, layerCallback)
Updating:Symbol 1
Updating:Symbol 2
Updating:Symbol 3
...

layers(context, layerCallback, frameCallback, elementCallback)

Iterates through a Timeline's layers, and optionally Frames and Elements, processing each one with a callback if supplied

Parameters:

  • context SymbolItem A SymbolInstance object
  • context Array An Array of layers
  • context Boolean Pass true to use the current timeline's layers
  • context Context A Context object, with a valid item reference
  • layerCallback Function An optional callback of the format function(layer, index, layers, context)
  • frameCallback Function An optional callback of the format function(frame, index, frames, context)
  • elementCallback Function An optional callback of the format function(element, index, elements, context)

Returns:

  •   Boolean true as soon as the callback returns true, if not false

The following example iterates over all layers in the current timeline, and if a named instance exists on frame 0, it renames the layer after it:

function layerCallback(layer, index, layers, context)
{
	context.setFrame(0);
	if(context.frame.elements.length)
	{
		layer.name = context.frame.elements[0]['name'] || layer.name;
	}
}

Iterators.layers(layerCallback);

Note that the frame context needs to be set each time, as the Context passed to the callback only has dom, timeline and frame properties set.

frames(context, frameCallback, elementCallback)

Iterates through a Layer's Frames, and optionally Elements, processing each one with a callback if supplied

Parameters:

  • context Layer A Layer
  • context Number A valid index of the current timeline
  • context String A valid layer name of the current timeline
  • context Context A Context object with a valid timeline reference
  • frameCallback Function An optional callback of the format function(frame, index, frames, context)
  • elementCallback Function An optional callback of the format function(element, index, elements, context)

Returns:

  •   Boolean true as soon as the callback returns true, if not false

The following example sets the tween sync to true for all frames on the animation layer:

function frameCallback(frame, index, frames, context)
{
	frame.motionTweenSync = true;
}

Iterators.frames('animation', frameCallback);

You could also acheive the same result using the Context class::

var keyframes = Context.create().setLayer('animation').keyframes;
for each(var frame in keyframes)
{
     frameCallback(frame);
}

Or you could update all animated frames by using Iterators.layers to loop over all layers.

elements(context, elementCallback)

Iterates through a Frame's elements, processing each one with a callback if supplied

Parameters:

  • context Frame A frame object in the current Timeline
  • context Context A Context object with a valid Frame reference
  • elementCallback Function A callback of the format function(element, index, elements, context)

Returns:

  •   Boolean true as soon as the callback returns true, if not false

The following example simply traces out the selected elements' names:

function elementCallback(element)
{
	trace(element.name);
}
elements($selection, elementCallback);	
Symbol_1
Symbol_2
Symbol_3

One Response to Iterators (library)

  1. Chichi Latté says:

    Is it just me or are layers iterated from top-to-bottom, but frame elements iterate bottom-to-top? Makes it weirdly hard to get a list of elements ordered by depth. Here’s what works for me…


    xjsfl.init(this);
    var doc = fl.getDocumentDOM();
    var timeline = fl.getDocumentDOM().getTimeline();

    Iterators.layers(timeline, null, frameCallback);

    function frameCallback(frame) {
    var elements = frame.elements.sort(function(a,b){
    return a.depth - b.depth
    });
    for (var i=0, len=elements.length; i < len; i++) {
    processElement(elements[i]);
    }
    }

    function processElement(elem) {
    // Do what you want with each element here
    fl.trace(elem.name);
    }

    Note: I’ve never used xJSFL before so this may be a crazy way to do it! Seems strange it should be this complicated.