# Source: ui/repetition.reel/repetition.js

    /**
     * @module "montage/ui/repetition.reel"
     */
    var Montage = require("../../core/core").Montage;
    var Component = require("../component").Component;
    var RangeController = require("../../core/range-controller").RangeController;
    var Promise = require("../../core/promise").Promise;
    var PressComposer = require("../../composer/press-composer").PressComposer;

    var Map = require("collections/map");
    var Set = require("collections/set");
    var logger = require("../../core/logger").logger("repetition").color.magenta();

    var TIMEOUT_BEFORE_ITERATION_BECOME_ACTIVE = 60;

    /**
     * A reusable view-model for each iteration of a repetition.  Each iteration
     * corresponds to a value from the {@link Repetition#contentController}.
     * When an iteration is drawn, it is tied to the corresponding controller-model
     * that carries which object the iteration is coupled to, and whether it is
     * selected.
     *
     * @class Iteration
     * @extends Montage
     */
    var Iteration = exports.Iteration = Montage.specialize( /** @lends Iteration.prototype # */ {

        /**
         * The parent repetition component.
         * @private
         */
        repetition: {value: null},

        /**
         * The repetition gets iterations from its `contentController`.  The
         * controller is responsible for tracking which iterations are drawn and
         * which are selected.  The iteration view-model is attached to the
         * controller view-model by this property. The `selected` and `object`
         * properties are bound to the eponymous properties of the iteration
         * controller.
         * @private
         */
        controller: {value: null},

        /**
         * The corresponding content for this iteration.
         * @type {Object}
         */

        _object: {
            value: null
        },

        object: {
            get: function () {
                return this._object;
            },
            set: function (value) {
                var selected;

                if (this._object !== value) {
                    this._object = value;
                    selected = this.repetition.contentController.selection.indexOf(value) !== -1;
                    if (this._selected !== selected) {
                        this.selected = selected;
                    }
                }
            }
        },

        /**
         * A `DocumentFragment`, donated by the repetition's `_iterationTemplate`
         * n&eacute;e `innerTemplate` which contains the elements that the
         * iteration owns when they are not on the document between the top and
         * bottom boundaries.
         * @private
         */
        _fragment: {value: null},

        /**
         * @private
         */
        _childComponents: {value: null},

        /**
         * The position of this iteration within the content controller, and within
         * the document immediately after the repetition has drawn.
         * @type {number}
         */
        index: {value: null},

        /**
         * The position of this iteration on the document last time it was drawn,
         * and its position within the `repetition.drawnIterations`.
         * @private
         */
        _drawnIndex: {value: null},

        /**
         * Whether this iteration should be highlighted.  It might be highlighted
         * because the user is touching it, or because it is under some other user
         * cursor as in an autocomplete popdown where the arrow keys manipulate the
         * active iteration.
         * @type {boolean}
         */
        _active: {
            value: false
        },

        active: {
            set: function (active) {
                active = !!active;

                if (this.active !== active) {
                    this._active = active;

                    if (this.repetition) {
                        if (active) {
                            this.repetition.activeIterations.add(this);
                        } else {
                            this.repetition.activeIterations.delete(this);
                        }

                        this._updateRepetitionDirtyClassIteration();
                    }
                }

            },
            get: function () {
                return this._active;
            }
        },

        /**
         * A flag that indicates that the "no-transition" CSS class should be added
         * to every element in the iteration in the next draw, and promptly removed
         * the draw thereafter.
         * @private
         */
        _noTransition: {value: null},

        /**
         * The document part created by instantiating the iteration template.
         */
        _templateDocumentPart: {value: null},

        /**
         * Set when the contents of the iteration no longer match their template.
         */
        isDirty: {value: false},

        _selected: {
            value: false
        },

        selected: {
            get: function () {
                return this._selected;
            },
            set: function (value) {
                value = !!value;
                if (this.object && this.repetition && this.repetition.contentController) {
                    if (value) {
                        this.repetition.contentController.selection.add(this.object);
                    } else {
                        this.repetition.contentController.selection.delete(this.object);
                    }
                }
                if (this._selected !== value) {
                    this._selected = value;
                    this._updateRepetitionDirtyClassIteration();
                }
            }
        },

        /**
         * Creates the initial values of all instance state.
         * @private
         */
        constructor: {
            value: function Iteration() {
                if (logger.isDebug) {
                    logger.debug("Iteration:%s create iteration %O", Object.hash(this), this);
                }

                this.repetition = null;
                this.controller = null;
                this.object = null;
                // An iteration can be "on" or "off" the document.  When the
                // iteration is added to a document, the "fragment" is depopulated
                // and placed between "topBoundary" and "bottomBoundary" on the
                // DOM.  The repetition manages the boundary markers around each
                // drawn index.
                this._fragment = null;

                this._childComponents = null;
                // The position that this iteration occupies in the controller.
                // This is updated synchronously in response to changes to
                // repetition.iterations, which are in turn synchronized with
                // controller.iterations.  The drawnIndex tracks the index by the
                // end of the next Repetition.draw.
                this.index = null;
                // The position that this iteration occupies in the repetition.
                // This is updated whenever views are added or removed before it in
                // the sequence, an operation of linear complexity but which is not
                // onerous since there should be a managable, fixed-maximum number
                // of drawn iterations.
                this._drawnIndex = null;

                // Changes to whether a user is touching the iteration are
                // reflected by the "active" CSS class on each element in the
                // iteration.  This gets updated in the draw cycle, in response to
                // operations that handlePropertyChange adds to the repetition draw
                // cycle.
                //Fixme: Need to find the purpose of this property, probably dead code.
                this._noTransition = false;

                // dispatch handlePropertyChange:
                this.addOwnPropertyChangeListener("_noTransition", this);

                this.addPathChangeListener(
                    "index.defined() && _childComponents.defined()",
                    this,
                    "handleComponentModelChange"
                );

                this.cachedFirstElement = null;

            }
        },

        _timeoutBecomeActiveID: {
            value: null
        },

        _shouldBecomeActive: {
            value: false
        },

        shouldBecomeActive: {
            set: function (bool) {
                if (this._timeoutBecomeActiveID) {
                    clearTimeout(this._timeoutBecomeActiveID);
                    this._timeoutBecomeActiveID = null;
                }

                if (bool) {
                    var self = this;
                    this._shouldBecomeActive = true;

                    this._timeoutBecomeActiveID = setTimeout(function () {
                        if (self._shouldBecomeActive) {
                            self.active = true;
                        }

                        self._shouldBecomeActive = false;
                    }, TIMEOUT_BEFORE_ITERATION_BECOME_ACTIVE);
                } else {
                    this._shouldBecomeActive = false;
                }
            },
            get: function () {
                return this._shouldBecomeActive;
            }
        },

        /**
         * Associates the iteration instance with a repetition.
         * @private
         */
        initWithRepetition: {
            value: function (repetition) {
                this.repetition = repetition;
                return this;
            }
        },

        /**
         * Disassociates an iteration with its content and prepares it to be
         * recycled on the repetition's list of free iterations.  This function is
         * called by handleOrganizedContentRangeChange when it recycles an
         * iteration.
         * @private
         */
        recycle: {
            value: function () {
                this.index = null;
                this.object = null;
                // Adding the "no-transition" class ensures that the iteration will
                // stop any transitions applied when the iteration was bound to
                // other content.  It has the side-effect of scheduling a draw, and
                // in that draw scheduling another draw to remove the
                // "no-transition" class.
                this._noTransition = true;
            }
        },

        /**
         * Injects this iteration to the document between its top and bottom
         * boundaries.
         * @param {number} index The drawn index at which to place the iteration.
         * @private
         */
        injectIntoDocument: {
            value: function (index) {
                if (this._drawnIndex !== null) {
                    if (logger.isDebug) {
                        logger.debug("Iteration:%s retracting from index %s and injecting at %s",Object.hash(this),this._drawnIndex, index);
                    }
                    this.retractFromDocument();
                } else {
                    if (logger.isDebug) {
                        logger.debug("Iteration:%s injecting at index %s",Object.hash(this),index);
                    }
                }

                var self = this;
                var repetition = this.repetition;
                var element = repetition.element;
                var boundaries = repetition._boundaries;

                // Add a new top boundary before the next iteration
                var topBoundary = element.ownerDocument.createTextNode("");
                var bottomBoundary = boundaries[index]; // previous
                boundaries.splice(index, 0, topBoundary);
                element.insertBefore(topBoundary, bottomBoundary);

                // Inject the elements into the document
                element.insertBefore(this._fragment, bottomBoundary);

                repetition._drawnIterations.splice(index, 0, this);
                repetition._updateDrawnIndexes(index);
                repetition._addDirtyClassListIteration(this);

                if (this._elementsWillBeAddedToMap) {
                    return;
                }

                // Once the child components have drawn once, and thus created all
                // their elements, we can add them to the _iterationForElement map
                var childComponentsLeftToDraw = this._childComponents.length;

                var firstDraw = function (event) {
                    event.target.removeEventListener("firstDraw", firstDraw, false);
                    childComponentsLeftToDraw--;
                    if (!childComponentsLeftToDraw) {
                        self.forEachElement(function (element) {
                            repetition._iterationForElement.set(element, self);
                        });
                    }
                };

                // notify the components to wake up and smell the document
                if (this._childComponents.length > 0) {
                    for (var i = 0; i < this._childComponents.length; i++) {
                        var childComponent = this._childComponents[i];
                        childComponent.addEventListener("firstDraw", firstDraw, false);
                        childComponent.needsDraw = true;
                        if (childComponent._completedFirstDraw === true) {
                            throw Error("Repetiton:%s child component %O has already drawn.", Object.hash(this), childComponent);
                        }
                    }
                } else {
                    this.forEachElement(function (element) {
                        repetition._iterationForElement.set(element, self);
                    });
                }
                this._elementsWillBeAddedToMap = true;
            }
        },

        _elementsWillBeAddedToMap: {
            value: false
        },

        /**
         * Retracts an iteration from the document, scooping its child nodes into
         * its DOMFragment.
         * @private
         */
        retractFromDocument: {
            value: function () {
                if (logger.isDebug) {
                    logger.debug("Iteration:%s retractFromDocument drawnIndex: %s",Object.hash(this), this._drawnIndex);
                }
                var index = this._drawnIndex;
                var repetition = this.repetition;
                var element = repetition.element;
                var topBoundary = repetition._boundaries[index];
                var bottomBoundary = repetition._boundaries[index + 1];

                // Remove the elements between the boundaries.  Also remove the top
                // boundary and adjust the boundaries array accordingly so future
                // injections and retractions can find their corresponding
                // boundaries.
                repetition._boundaries.splice(index, 1);
                var fragment = this._fragment;
                var child = topBoundary.nextSibling;
                while (child !== bottomBoundary) {
                    var next = child.nextSibling;
                    element.removeChild(child);
                    fragment.appendChild(child);
                    child = next;
                }
                element.removeChild(topBoundary);

                this._drawnIndex = null;
                repetition._drawnIterations.splice(index, 1);
                repetition._updateDrawnIndexes(index);
            }
        },

        /**
         * This is a method that responds to changes (and the initial value of) the
         * FRB expression `index.defined() && _childComponents.defined()`.
         * @private
         */
        handleComponentModelChange: {
            value: function (onComponentModel) {
                var childComponents = this._childComponents,
                    repetition = this.repetition,
                    i, length;

                if (onComponentModel) {
                    for (i = 0, length = childComponents.length; i < length; i++) {
                        repetition.addChildComponent(childComponents[i]);
                    }
                // the second condition protects against removing before adding in
                // the initial state.
                } else if (this._childComponents) {
                    for (i = 0, length = childComponents.length; i < length; i++) {
                        repetition.removeChildComponent(childComponents[i]);
                    }
                }
            }
        },

        /**
         * Dispatched by the "active" and "selected" property change listeners to
         * notify the repetition that these iterations need to have their CSS class
         * lists updated.
         * @private
         */
        _updateRepetitionDirtyClassIteration: {
            value: function () {
                if (!this.repetition) {
                    return;
                }
                this.repetition._addDirtyClassListIteration(this);
                this.repetition.needsDraw = true;
            }
        },

        /**
         * A utility method for applying changes to every element in this iteration
         * if it is on the document.  This may be safely called on a retracted
         * iteration with no effect.
         * @private
         */
        forEachElement: {
            value: function (callback, thisp) {
                var repetition = this.repetition;
                var index = this._drawnIndex;
                // Short-circuit if the iteration is not on the document.
                if (index === null || index === undefined) {
                    return;
                }

                for (
                    var child = repetition._boundaries[index];
                    child !== repetition._boundaries[index + 1];
                    child = child.nextSibling
                ) {
                    if (child.nodeType === 1) { // tags
                        callback.call(thisp, child);
                    }
                }
            }
        },

        /**
         * The first tag node inside this iteration.  This is an accessor.  The
         * accessor function searches for the first element every time
         * it is accessed, to protect against changes to the structure within
         * the iteration.
         *
         * The accessor stores its result in `cachedFirstElement`.  If you are
         * certain that the internal structure of the repetition is consistent and
         * have accessed `firstElement` at least once before, you can take
         * advantage of quick access to `cachedFirstElement`.
         * @private
         */
        firstElement: {
            get: function () {
                var repetition = this.repetition;
                var index = this._drawnIndex;

                // Short-circuit if the iteration is not on the document.
                if (index === null || index === undefined) {
                    return;
                }

                for (
                    var child = repetition._boundaries[index];
                    child !== repetition._boundaries[index + 1];
                    child = child.nextSibling
                ) {
                    if (child.nodeType === 1) { // tags
                        this.cachedFirstElement = child;
                        return child;
                    }
                }
            }
        },

        isComponentTreeLoaded: {
            value: function () {
                return this._fragment !== null;
            }
        },

        /**
         * The most recent result of the `firstElement` accessor, useful for speed
         * if you know that the internal structure of the iteration is static.
         * @private
         */
        cachedFirstElement: {
            value: null
        }

    });

    Iteration.prototype.handlePropertyChange = Iteration.prototype._updateRepetitionDirtyClassIteration;

    // Here it is, what we have all been waiting for, the prototype of the hour.
    // Give it up for the Repetition...

    /**
     * @class Repetition
     * @classdesc A component that repeats its inner template for each value in
     * @desc
     * A component that manages copies of its inner template for each value in its
     * content.  The content is managed by a controller.  The repetition will
     * create a {@link RangeController} for the content if you provide a
     * [content]{@link Repetition#content} property instead of a
     * [contentController]{@link Repetition#contentController}.
     *
     * Ensures that the document contains iterations in the same order as provided
     * by the content controller.
     *
     * The repetition strives to avoid moving iterations on, off, or around on the
     * document, prefering to inject or retract iterations between ones that remain
     * in their respective order, or even just rebind existing iterations to
     * alternate content instead of injecting and retracting in the same position.
     * some content.
     *
     * @extends Component
     */
    var Repetition = exports.Repetition = Component.specialize(/** @lends Repetition.prototype # */{

        // For the creator:
        // ----

        /**
         * Imperatively initializes a repetition with content.  You can alternately
         * bind the `content` property of a repetition without initializing.  You
         * should not use the `contentController` property of the repetition if you
         * are initialized with the `content` property.
         * @private
         */
        initWithContent: {
            value: function (content) {
                this.object = content;
                return this;
            }
        },

        /**
         * Imperatively initializes a repetition with a content controller, like a
         * `RangeController`.  You can alternately bind the `contentController`
         * property of a repetition without initializing.  You should not use the
         * `content` property of a repetition if you are using its
         * `contentController`.
         * @private
         */
        initWithContentController: {
            value: function (contentController) {
                this.contentController = contentController;
                return this;
            }
        },

        /**
         * A getter and setter for the content of a repetition.  If you set the
         * content property of a repetition, it produces a range content controller
         * for you.  If you get the content property, it will reach into the
         * content controller to give you its content.
         *
         * The content represents the entire backing collection.  The content
         * controller may filter, sort, or otherwise manipulate the visible region
         * of the content.  The {@link Iteration#index} of each iteration
         * corresponds to the position within the visible region of the controller.
         * @type {Array.<Object>}
         */

        content: {
            get: function () {
                if (this.contentController) {
                    return this.contentController.content;
                }
                return null;
            },
            set: function (value) {
                this.contentController.content = value;
            }
        },

        /**
         * A range controller or instance with the same interface (
         * [iterations]{@link Repetition#iterations} and
         * [selection]{@link Repetition#selection} properties, where each
         * <iteration has `object` and `selected` properties).  The controller is
         * responsible for managing which contents are visible, selected, and the
         * order of their appearance.
         * @type {RangeController}
         */
        _contentController: {
            value: null
        },

        contentController: {
            set: function (contentController) {
                if (this._contentController !== contentController) {
                    this._contentController = contentController;
                }
            },
            get: function () {
                if (!this._contentController) {
                    this._contentController = new RangeController();
                }

                return this._contentController;
            }
        },

        /**
         * When selection is enabled, each element in an iteration responds to
         * touch and click events such that the iteration is highlighted (with the
         * "active" CSS class) when the user touches or clicks it, and toggles
         * whether the corresponding content is selected.
         *
         * Selection may be enabled and disabled at any time.  The repetition
         * watches changes to this property.
         *
         * All repetitions support selection, whether it is used or not.  This
         * property merely dictates whether the repetition handles gestures for
         * selection.
         * @type {boolean}
         */
        isSelectionEnabled: {value: null},

        allowsMultipleSelection: {
            set: function (allowsMultipleSelection) {
                allowsMultipleSelection = !!allowsMultipleSelection;

                if (this.contentController.allowsMultipleSelection !== allowsMultipleSelection) {
                    this.contentController.allowsMultipleSelection = allowsMultipleSelection;
                }

                if (allowsMultipleSelection && !this.isSelectionEnabled) {
                    this.isSelectionEnabled = true;
                }
            },
            get: function () {
                return this.contentController !== void 0 && this.contentController !== null ?
                    this.contentController.allowsMultipleSelection : false;
            }
        },

        /**
         * The repetition maintains an array of every visible, selected iteration,
         * in the order of its appearance.  The user should not modify the selected
         * iterations array.
         * @type {Array.<Iteration>}
         */
        selectedIterations: {value: null},

        /**
         * The repetition maintains an array of the indexes of every selected
         * iteration.  The user should not modify the array.
         * @type {Array.<number>}
         */
        selectedIndexes: {value: null},

        /**
         * The user may determine which iterations are active by setting or
         * manipulating the content of the `activeIterations` array.  At present,
         * the repetition does not guarantee any particular order of appearance of
         * the contained iterations.
         * @type {Array.<Iteration>}
         */
        activeIterations: {value: null},

        /**
         * The repetition coordinates this array of repetition iterations.  Each
         * iteration tracks its corresponding content, whether it is selected,
         * whether it is active, and what CSS classes are applied on each of its
         * direct child nodes.  This array appears in the order that the iterations
         * will be drawn.  There is one repetition iteration for each controller
         * iteration.  The repetition iterations have more responsibilities than
         * the corresponding controller, but some of the properties are bound by
         * the same names, like `object` and `selected`.
         * @type {Array.<Iteration>}
         */
        iterations: {value: null},

        // For the template:
        // ----

        /**
         * Informs the super-type, `Component`, that there is no `repetition.html`.
         * @private
         */
        hasTemplate: {value: false},

        /**
         * A copy of `innerTemplate`, provided by the `Component` layer, that
         * produces the HTML and components for each iteration.  If this property
         * is `null`, it signifies that the template is in transition, either
         * during initialization or due to resetting `innerTemplate`.  In either
         * case, it is a reliable indicator that the repetition is responding to
         * controller iteration range changes, since that requires a functioning
         * template.
         * @private
         */
        _iterationTemplate: {value: null},

        /**
         * Informs Template that it is not safe to reference the initial DOM
         * contents of the repetition.
         * @see Component.clonesChildComponents
         * @private
         */
        clonesChildComponents: {value: true},

        __pressComposer: {value: null},

        _pressComposer: {
            get: function () {
                if (!this.__pressComposer) {
                    this.__pressComposer = new PressComposer();
                    this.__pressComposer.lazyLoad = true;
                    this.addComposerForElement(this.__pressComposer, this.element);
                }

                return this.__pressComposer;
            }
        },

        // Implementation:
        // ----

        _cancelSelectionRangeChangeListener: {
            value: null
        },

        _selection: {
            value: null
        },

        selection: {
            get: function () {
                return this._selection;
            },
            set: function (value) {
                if (this.contentController) {
                    if (this.contentController.selection !== value) {
                        this.contentController.selection = value;
                    }
                    if (this._selection !== this.contentController.selection) {
                        this._selection = this.contentController.selection;
                    }
                    if (this._cancelSelectionRangeChangeListener) {
                        this._cancelSelectionRangeChangeListener();
                    }
                    if (value) {
                        this._cancelSelectionRangeChangeListener = (
                            this.contentController.selection.addRangeChangeListener(this, "selection")
                        );
                        this.handleSelectionRangeChange(value, []);
                    } else {
                        this._cancelSelectionRangeChangeListener = null;
                    }
                } else {
                    this._selection = value;
                }
            }
        },

        handleSelectionRangeChange: {
            value: function (add, remove) {
                if (this.iterations) {
                    var iterationsMap,
                        length = this.iterations.length,
                        objectIterations,
                        object,
                        iteration,
                        i, j;

                    if ((add.length <= 1) && (remove.length <= 1)) {
                        if (remove.length) {
                            object = remove[0];
                            for (i = 0; i < length; i++) {
                                if (this.iterations[i].object === object) {
                                    this.iterations[i].selected = false;
                                }
                            }
                        }
                        if (add.length) {
                            object = add[0];
                            for (i = 0; i < length; i++) {
                                if (this.iterations[i].object === object) {
                                    this.iterations[i].selected = true;
                                }
                            }
                        }
                    } else {
                        iterationsMap = new Map();
                        for (i = 0; i < length; i++) {
                            iteration = this.iterations[i];
                            object = iteration.object;
                            if (!(objectIterations = iterationsMap.get(object))) {
                                objectIterations = [];
                                iterationsMap.set(object, objectIterations);
                            }
                            objectIterations.push(iteration);
                        }
                        for (i = 0; i < remove.length; i++) {
                            if ((objectIterations = iterationsMap.get(remove[i]))) {
                                for (j = 0; j < objectIterations.length; j++) {
                                    objectIterations[j].selected = false;
                                }
                            }
                        }
                        for (i = 0; i < add.length; i++) {
                            if ((objectIterations = iterationsMap.get(add[i]))) {
                                for (j = 0; j < objectIterations.length; j++) {
                                    objectIterations[j].selected = true;
                                }
                            }
                        }
                    }
                }
            }
        },

        _visibleIndexes: {
            value: null
        },

        visibleIndexes: {
            get: function () {
                return this._visibleIndexes;
            },
            set: function (value) {
                if (this._visibleIndexes !== value) {
                    if (this._visibleIndexes && this._visibleIndexes.removeRangeChangeListener) {
                        this._visibleIndexes.removeRangeChangeListener(this, "visibleIndexes");
                    }
                    this._visibleIndexes = value;
                    if (this._visibleIndexes && this._visibleIndexes.addRangeChangeListener) {
                        this._visibleIndexes.addRangeChangeListener(this, "visibleIndexes");
                    }
                    this._updateOrganizedContent();
                }
            }
        },

        handleVisibleIndexesRangeChange: {
            value: function (plus, minus, index) {
                var plusContent,
                    i;

                if (this.__controllerOrganizedContent) {
                    plusContent = [];
                    for (i = 0; i < plus.length; i++) {
                        plusContent.push(this.__controllerOrganizedContent[plus[i]]);
                    }
                    this.organizedContent.swap(index, minus.length, plusContent);
                    if (this._isListeningToOrganizedContentChanges) {
                        this.handleOrganizedContentRangeChange(plusContent.length, minus.length, index);
                        this.needsDraw = true;
                    }
                }
            }
        },

        __controllerOrganizedContent: {
            value: null
        },

        _controllerOrganizedContent: {
            get: function () {
                return this.__controllerOrganizedContent;
            },
            set: function (value) {
                if (this.__controllerOrganizedContent !== value) {
                    if (this.__controllerOrganizedContent && this.__controllerOrganizedContent.removeRangeChangeListener) {
                        this.__controllerOrganizedContent.removeRangeChangeListener(this, "controllerOrganizedContent");
                    }
                    this.__controllerOrganizedContent = value;
                    if (this.__controllerOrganizedContent && this.__controllerOrganizedContent.addRangeChangeListener) {
                        this.__controllerOrganizedContent.addRangeChangeListener(this, "controllerOrganizedContent");
                    }
                    this._updateOrganizedContent();
                }
            }
        },

        handleControllerOrganizedContentRangeChange: {
            value: function (plus, minus, index) {
                if (!this._visibleIndexes) {
                    this.organizedContent.swap(index, minus.length, plus);
                    if (this._isListeningToOrganizedContentChanges) {
                        this.handleOrganizedContentRangeChange(plus.length, minus.length, index);
                        this.needsDraw = true;
                    }
                } else {
                    this._updateOrganizedContent();
                }
            }
        },

        _updateOrganizedContent: {
            value: function () {
                var previousLength,
                    i;

                if (this.__controllerOrganizedContent) {
                    if (this._visibleIndexes) {
                        if (this.organizedContent.length > this._visibleIndexes.length) {
                            this.organizedContent.length = this._visibleIndexes.length;
                            if (this._isListeningToOrganizedContentChanges) {
                                this.handleOrganizedContentRangeChange(0, this.organizedContent.length - this._visibleIndexes.length, this._visibleIndexes.length);
                            }
                        }
                        previousLength = this.organizedContent.length;
                        for (i = 0; i < this._visibleIndexes.length; i++) {
                            if (this.organizedContent[i] !== this.__controllerOrganizedContent[this._visibleIndexes[i]]) {
                                this.organizedContent[i] = this.__controllerOrganizedContent[this._visibleIndexes[i]];
                                if (this._isListeningToOrganizedContentChanges) {
                                    this.handleOrganizedContentRangeChange(1, previousLength > i ? 1 : 0, i);
                                }
                            }
                        }
                        if (this._isListeningToOrganizedContentChanges) {
                            this.needsDraw = true;
                        }
                    } else {
                        previousLength = this.organizedContent.length;
                        this.organizedContent.swap(0, previousLength, this.__controllerOrganizedContent);
                        if (this._isListeningToOrganizedContentChanges) {
                            this.handleOrganizedContentRangeChange(this.__controllerOrganizedContent.length, previousLength, 0);
                            this.needsDraw = true;
                        }
                    }
                } else {
                    previousLength = this.organizedContent.length;
                    this.organizedContent = [];
                    if (this._isListeningToOrganizedContentChanges) {
                        this.handleOrganizedContentRangeChange(0, previousLength, 0);
                        this.needsDraw = true;
                    }
                }
            }
        },

        /**
         * @private
         */
        constructor: {
            value: function Repetition() {
                // XXX Note: Any property added to initialize in constructor must
                // also be accounted for in _teardownIterationTemplate to reset the
                // repetition.

                this.contentController = null;
                this.organizedContent = [];
                this.defineBinding("_controllerOrganizedContent", {
                    "<-": "contentController.organizedContent"
                });
                // Determines whether the repetition listens for mouse and touch
                // events to select iterations, which involves "activating" the
                // iteration when the user touches.
                this.isSelectionEnabled = false;
                this.defineBinding("selection", {
                    "<-": "contentController.selection"
                });
                this.defineBinding("selectedIterations", {
                    "<-": "iterations.filter{selected}"
                });
                this.defineBinding("selectedIndexes", {
                    "<-": "selectedIterations.map{index}"
                });
                this.defineBinding("allowsMultipleSelection", {
                    "<-": "contentController.allowsMultipleSelection"
                });

                // The iteration template:
                // ---

                // The template that gets repeated in the DOM
                this._iterationTemplate = null;

                // This triggers the setup of the iteration template
                this.addPathChangeListener(
                    this._setupRequirements,
                    this,
                    "_handleSetupRequirementsChange"
                );

                // This triggers the teardown of an iteration template.
                this.addPathChangeListener(
                    "innerTemplate",
                    this,
                    "_handleInnerTemplateChange"
                );

                // The state of the DOM:
                // ---

                // The "iterations" array tracks "_controllerIterations"
                // synchronously.  Each iteration corresponds to controlled content
                // at its visible position.  An iteration has an instance of the
                // iteration template / inner template.
                this.iterations = [];
                // The "_drawnIterations" array gets synchronized with
                // "iterations" by applying draw operations when "Repetition.draw"
                // occurs.
                this._drawnIterations = [];
                // Iteration content can be reused.  When an iteration is collected
                // (and when it is initially created), it gets put in the
                // _freeIterations list.
                this._freeIterations = []; // push/pop LIFO
                // We track the direct child nodes of every iteration so we can
                // look up which iteration a mouse or touch event occurs on, for
                // the purpose of selection tracking.
                this._iterationForElement = new Map();

                // This promise synchronizes the creation of new iterations.
                this._iterationCreationPromise = Promise.resolve();

                // Where we want to be after the next draw:
                // ---

                // The _boundaries array contains comment nodes that serve as the
                // top and bottom boundary of each iteration.  There will always be
                // one more boundary than iteration.
                this._boundaries = [];

                // The plan for the next draw to synchronize _controllerIterations
                // and iterations on the DOM:
                // ---

                this._dirtyClassListIterations = new Set();
                // We can draw when we have created all requested iterations.
                this._requestedIterations = 0;
                this._createdIterations = 0;
                this._canDrawInitialContent = false;
                this._initialContentDrawn = false;

                // Selection gestures
                // ------------------

                this.addOwnPropertyChangeListener("isSelectionEnabled", this);
                // Used by selection tracking (last part of Repetition
                // implementation) to track which selection pointer the repetition
                // is monitoring
                this._selectionPointer = null;
                // This is a list of iterations that are active.  It is maintained
                // entirely by a bidirectional binding to each iteration's "active"
                // property, which in turn manages the "active" class on each
                // element in the iteration in the draw cycle.  Iterations are
                // activated by gestures when selection is enabled, and can also be
                // managed manually for a cursor, as in an autocomplete drop-down.
                // TODO Provide some assurance that the activeIterations will
                // always appear in the same order as they appear in the iterations
                // list.
                this.activeIterations = [];

            }
        },

        // Creating an iteration template:
        // ----

        /**
         * This is an FRB expression that becomes true when all of the requirements
         * for setting up an iteration template have been satisfied.
         * -   A component is not able to get its innerTemplate before being
         *     completely deserialized from the template and self means having
         *     access to its ownerDocumentPart.  This will happen when the
         *     repetition is asked to load its component tree during template
         *     instantiation.
         * -   We shouldn't set up the iteration template if the repetition
         *     received new content, we'll wait until contentDidLoad is called.
         *     The problem is that the new components from the new DOM are already
         *     in the component tree but not in the DOM, and since self function
         *     removes the child components from the repetition we lose them
         *     forever.
         * @private
         */
        _setupRequirements: {
            value: "[!_iterationTemplate.defined(),!_newDomContent.defined(),!_shouldClearDomContentOnNextDraw,_isComponentExpanded,_ownerDocumentPart.defined()].every{}"
        },

        /**
         * This is the rising-edge trigger for setting up the iteration template.
         * When the `_setupRequirements` expression becomes true, it is time to set
         * up the iteration template based on the inner template.
         * @private
         */
        _handleSetupRequirementsChange: {
            value: function (canSetUp) {
                if (canSetUp) {
                    this._setupIterationTemplate();
                }
            }
        },

        /**
         * This is the falling-edge trigger that tears down the iteration template.
         * A new iteration template will be created if or when an inner template
         * is provided and all the requirements are satisfied again.
         * @private
         */
        _handleInnerTemplateChange: {
            value: function (innerTemplate) {
                if (this._iterationTemplate) {
                    this._teardownIterationTemplate();
                }
                if (innerTemplate && this.getPath(this._setupRequirements)) {
                    this._setupIterationTemplate();
                }
            }
        },

        /**
         * Prepares this component and all its children for garbage collection
         * (permanently) or reuse.
         *
         * @param permanently whether to cancel bindings on this component
         * and all of its descendants in the component tree.
         * @private
         */
        cleanupDeletedComponentTree: {
            value: function (permanently) {
                // Don't set innerTemplate directly because the listener system
                // will get it and that will make the repetition to create it all
                // over again if it happens to be null for some reason.
                var previousIterationTemplate = this._innerTemplate;
                this._innerTemplate = null;
                if (previousIterationTemplate) {
                    this._teardownIterationTemplate();
                }
                if (permanently) {
                    this.cancelBindings();
                }
            }
        },

        /**
         * Called by Component to build the component tree.
         * @private
         */
        expandComponent: {
            value: function expandComponent() {
                // Setting this property to true *causes* _setupIterationTemplate
                // to be run through the handleSetupRequirementsChange listener,
                // and as it runs synchronously, guarantees that the template will
                // be expanded before the next line.
                this._isComponentExpanded = true;
                // TODO should this ever become false?
                return Promise.resolve();
            }
        },

        _buildIterationTemplate: {
            value: function () {
                var iterationTemplate;
                var serialization;
                var serializationObject;
                var label;

                // We need to clone the innerTemplate because this repetition
                // might be used in different contexts and with different template
                // arguments making it having diferent external objects in its
                // instances.
                iterationTemplate = this.innerTemplate.clone();
                serialization = iterationTemplate.getSerialization();
                serializationObject = serialization.getSerializationObject();
                label = Montage.getInfoForObject(this).label;

                this._iterationLabel = label + ":iteration";
                serializationObject[this._iterationLabel] = {};
                iterationTemplate.setObjects(serializationObject);

                if (this.innerTemplate.hasParameters()) {
                    this._expandIterationTemplateParameters(iterationTemplate);
                }

                //jshint -W106
                if (window._montage_le_flag) {
                    iterationTemplate.refresher = this;
                    this._leTagIterationTemplate(iterationTemplate);
                }
                //jshint +W106

                return iterationTemplate;
            }
        },

        _rebuildIterationTemplate: {
            value: function () {
                var iterationTemplate = this._iterationTemplate,
                    newIterationTemplate,
                    iterations = this.iterations;

                this._purgeFreeIterations();
                for (var i = 0, iteration; (iteration = iterations[i]); i++) {
                    iteration.isDirty = true;
                }

                this._innerTemplate = null;
                newIterationTemplate = this._buildIterationTemplate();
                iterationTemplate.replaceContentsWithTemplate(newIterationTemplate);
            }
        },

        refreshTemplate: {
            value: function () {
                this._rebuildIterationTemplate();
            }
        },

        _isListeningToOrganizedContentChanges: {
            value: false
        },

        /**
         * When `_setupRequirements` have all been met, this method produces an
         * iteration template using the `innerTemplate` that has been given to this
         * repetition.  It also deletes any *initial* child components and starts
         * watching for changes to the organized content.  Watching for organized
         * content changes would cause errors if it were not possible to
         * instantiate iterations.  In symmetry, `_teardownIterationTemplate`
         * pauses watching the organized content.
         * @private
         */
        _setupIterationTemplate: {
            value: function () {
                this._iterationTemplate = this._buildIterationTemplate();
                // Erase the initial child component trees. The initial document
                // children will be purged on first draw.  We use the innerTemplate
                // as the iteration template and replicate it for each iteration
                // instead of using the initial DOM and components.
                var childComponents = this.childComponents;
                var childComponent;
                var index = childComponents.length - 1;
                // pop() each component instead of shift() to avoid bubbling the
                // indexes of each child component on every iteration.
                while ((childComponent = childComponents[index--])) {
                    childComponent.detachFromParentComponent();
                    childComponent.needsDraw = false;
                    childComponent.cleanupDeletedComponentTree(true); // cancel bindings, permanent
                }

                // Begin tracking the controller organizedContent.  We manually
                // dispatch a range change to track all the iterations that have
                // come and gone while we were not watching.
                this.handleOrganizedContentRangeChange(this.organizedContent.length, 0, 0);
                // Dispatches handleOrganizedContentRangeChange:
                this._isListeningToOrganizedContentChanges = true;
                this._canDrawInitialContent = true;
                this.needsDraw = true;

            }
        },

        _leTagIterationTemplate: {
            value: function (template) {
                var body = template.document.body;

                if (body.children.length > 0) {
                    //jshint -W106
                    var ownerModuleId = this.ownerComponent._montage_metadata.moduleId;
                    var label = this._montage_metadata.label;
                    //jshint +W106
                    this._leTagStarArgument(ownerModuleId, label, body);
                }
            }
        },

        /**
         * This method is used both in `cleanupDeletedComponentTree` and the
         * internal `_handleInnerTemplateChange` functions, to retract all drawn
         * iterations from the document, prepare all allocated iterations for
         * garbage collection, and pause observation of the controller's
         * iterations.
         * @private
         */
        _teardownIterationTemplate: {
            value: function () {

                // stop listenting to controlled content changes until the new
                // iteration template is ready.  (at which point we will manually
                // dispatch handleOrganizedContentRangeChange with the entire
                // content of the array when _setupIterationTemplate has finished)
                this._isListeningToOrganizedContentChanges = false;
                // simulate removal of all iterations from the controller to purge
                // the iterations and _drawnIterations.
                this.handleOrganizedContentRangeChange(0, this.organizedContent.length, 0);

                // prepare all the free iterations and their child component trees
                // for garbage collection
                this._purgeFreeIterations();

                // purge the existing iterations
                this._iterationTemplate = null;
                this._iterationForElement.clear();
                this._requestedIterations = 0;
                this._createdIterations = 0;
                this._canDrawInitialContent = false;
                this._selectionPointer = null;
                this.activeIterations.clear();
                this._dirtyClassListIterations.clear();
            }
        },

        _purgeFreeIterations: {
            value: function () {
                for (var i = 0; i < this._freeIterations.length; i++) {
                    var iteration = this._freeIterations[i];
                    for (var j = 0; j < iteration._childComponents.length; j++) {
                        var childComponent = iteration._childComponents[j];
                        this.removeChildComponent(childComponent);
                        childComponent.cleanupDeletedComponentTree(true); // true cancels bindings
                    }
                }
                this._freeIterations.clear();
            }
        },

        // TODO(@aadsm) doc
        /**
         * @private
         */
        _expandIterationTemplateParameters: {
            value: function (template) {
                var owner = this,
                    argumentsTemplate,
                    collisionTable,
                    externalLabels,
                    objects,
                    instances,
                    expansionResult,
                    newLabel,
                    labels,
                    metadata;

                // Crawl up the template chain while there are parameters to expand
                // in the iteration template.
                while (template.hasParameters()) {
                    owner = owner.ownerComponent;
                    argumentsTemplate = owner._ownerDocumentPart.template;
                    objects = owner._ownerDocumentPart.objects;

                    expansionResult = template.expandParameters(owner);

                    // Associate the new external objects with the objects in the
                    // instantiation of argumentsTemplate.
                    externalLabels = template.getSerialization()
                        .getExternalObjectLabels();
                    instances = template.getInstances();

                    labels = expansionResult.labels;
                    collisionTable = expansionResult.labelsCollisions;

                    for (var i = 0, label; (label = labels[i]); i++) {
                        if (collisionTable && label in collisionTable) {
                            newLabel = collisionTable[label];
                        } else {
                            newLabel = label;
                        }

                        // Setup external objects and configure the correct require,
                        // label and owner for the objects that came from the
                        // template arguments.
                        if (externalLabels.indexOf(newLabel) >= 0) {
                            instances[newLabel] = objects[label];
                        } else {
                            metadata = argumentsTemplate.getObjectMetadata(label);
                            if (!metadata.owner) {
                                metadata.owner = objects.owner;
                            }
                            template.setObjectMetadata(newLabel, metadata.require,
                                metadata.label, metadata.owner);
                        }
                    }
                }
            }
        },

        // Instantiating an iteration template:
        // ----

        _iterationLabel: {
            value: null
        },
        /**
         * We can only create one iteration at a time because it is an asynchronous
         * operation and the "repetition.currentIteration" property may be bound
         * during this process.  If we were to attempt to instantiate multiple
         * iterations asynchronously, currentIteration and contentAtCurrentIteration
         * bindings would get interleaved.  The "_iterationCreationPromise"
         * synchronizes "createIteration", ensuring we only create one at a time,
         * waiting for the previous to either succeed or fail before attempting
         * another.
         * @private
         */
        _iterationCreationPromise: {value: null},

        /**
         * Creates a new iteration and sets up a new instance of the iteration
         * template.  Ensures that only one iteration is being instantiated at a
         * time to guarantee that `currentIteration` can be reliably bound to the
         * particular iteration.
         * @private
         */
        _createIteration: {
            value: function () {
                var self = this,
                    iteration = new this.Iteration().initWithRepetition(this);

                this._iterationCreationPromise = this._iterationCreationPromise
                .then(function () {
                    var _document = self.element.ownerDocument,
                        instances,
                        promise;

                    // We need to extend the instances of the template to add the
                    // iteration object that is specific to each iteration template
                    // instance.
                    instances = self._iterationTemplate.getInstances();
                    instances = Object.create(instances);
                    instances[self._iterationLabel] = iteration;

                    promise = self._iterationTemplate.instantiateWithInstances(instances, _document)
                    .then(function (part) {
                        part.parentDocumentPart = self._ownerDocumentPart;
                        iteration._templateDocumentPart = part;
                        part.loadComponentTree().then(function () {
                            if (logger.isDebug) {
                                logger.debug("Iteration:%s component tree loaded.", Object.hash(iteration));
                            }
                            iteration._fragment = part.fragment;
                            // It is significant that _childComponents are assigned
                            // *after* the component tree has finished loading
                            // because this signals to the iteration that it should
                            // synchronize the child components with the repetition
                            // based on whether the iteration should be on the DOM
                            // hereafter.
                            iteration._childComponents = part.childComponents;
                            self.constructIteration(iteration);
                        });

                        return iteration;
                    });

                    return promise.then(null, function () {
                        // but regardless of whether this iteration failed, allow
                        // another iteration to be created
                    });
                });

                this._requestedIterations++;
                return iteration;
            }
        },

        /**
         * @private
         */
        // This utility method for the completion of _createIteration.
        constructIteration: {
            value: function (iteration) {
                this._createdIterations++;

                if (this._createdIterations >= this._requestedIterations) {
                    this.needsDraw = true;
                    // TODO: When we change the canDraw() function of a component
                    // we need to _canDraw = true whenever we request a draw.
                    // This is because if the component gets into a state where it
                    // is part of the draw cycle but not able to draw (canDraw()
                    // === false) its needsDraw property is not set to false and
                    // further needsDraw = true will result in a noop, the only way
                    // to make the component draw again is by informing the root
                    // component directly that it can draw now, and this is done by
                    // _canDraw = true. Another option is to make its parent draw,
                    // but we probably don't want that.
                    this._canDraw = true;
                }
            }
        },

        // Reacting to changes in the controlled visible content:
        // ----

        /**
         * The content controller produces an array of iterations.  The controller
         * may come and go, but each instance of a repetition has its own array to
         * track the corresponding content controller's content, which gets emptied
         * and refilled by a range content binding  when the controller changes.
         * This is to simplify management of the repetition's controller iterations
         * range change listener.
         *
         * The controller iterations themselves instruct the repetition to display
         * an iteration at the corresponding position, and provide a convenient
         * interface for getting and setting whether the corresponding content is
         * selected.
         * @private
         */
        _controllerIterations: {value: null},

        /**
         * The drawn iterations get synchronized with the `iterations` array each
         * time the repetition draws.  The `draw` method simply walks down the
         * iterations and drawn iterations arrays, redacting drawn iterations if
         * they are not at the correct position and injecting the proper iteration
         * from the model in its place.
         * @private
         */
        _drawnIterations: {value: null},

        /**
         * @private
         */
        _freeIterations: {value: null},

        /**
         * Reacts to changes in the controller's organized content by altering the
         * modeled iterations.  This may require additional iterations to be
         * instantiated.  The repetition may redraw when all of the instantiated
         * iterations have finished loading.
         *
         * This method is dispatched in response to changes to the organized
         * content but only while the repetition is prepared to instantiate
         * repetitions.  Any time the repetition needs to change its inner
         * template, or when it is setting up its initial inner template, the
         * repetition silences the organizedContent range change listener and
         * manually calls this method as if organizedContent were cleared out, to
         * cause all of the iterations to be collected and removed from the
         * document.  When the iteration template is ready again, it manually
         * dispatches this method again as if the organizedContent had been
         * repopulated, then resumes listening for changes.
         *
         * Bindings react instantly to the change in the iteration model.  The draw
         * method synchronizes `index` and `_drawnIndex` on each iteration as it
         * rearranges `_drawnIterations` to match the order and content of the
         * `iterations` array.
         *
         * @private
         */
        handleOrganizedContentRangeChange: {
            value: function (plusLength, minusLength, index) {
                var start = index,
                    freedIterations,
                    freedIteration,
                    newIterations, // isDebugOnly
                    iterations = this.iterations,
                    reusableIterationsCount = Math.min(plusLength, minusLength),
                    removeIterationsCount = minusLength - reusableIterationsCount,
                    addIterationsCount = plusLength - reusableIterationsCount,
                    organizedContent = this.organizedContent,
                    i, j;

                if (logger.isDebug) {
                    logger.debug("Repetition:%s content changed +%s@%s %O -%s %O ", Object.hash(this), plusLength?plusLength:0, index, minusLength?minusLength:0);
                    logger.debug("Repetition:%s +%s -%s iterations", Object.hash(this), addIterationsCount, removeIterationsCount);
                }
                if (this._iterationTemplate.isDirty) {
                    this._iterationTemplate.refresh();
                }

                // This is an optimization for a common case with the Flow that
                // avoids shifting around with the iterations and free iterations
                // array.  If the number of added and removed content values are
                // the same, which is what happens if a value is set at an index,
                // it is unnecessary to splice the corresponding index in and out,
                // and unnecessary to even check whether more iterations need to be
                // allocated.

                for (i = 0; i < reusableIterationsCount; i++, index++) {
                    iterations[index].object = organizedContent[index];
                }

                if (removeIterationsCount > 0) {
                    // Subtract iterations
                    freedIterations = iterations.splice(index, removeIterationsCount);

                    // Notify these iterations that they have been recycled,
                    // particularly so they know to disable animations with the
                    // "no-transition" CSS class.
                    // Add them back to the free list so they can be reused
                    for (i = 0; i < removeIterationsCount; i++) {
                        freedIteration = freedIterations[i];
                        freedIteration.recycle();
                        if (!freedIteration.isDirty) {
                            this._freeIterations.push(freedIteration);
                        }
                    }
                }

                if (logger.isDebug) {
                    newIterations = [];
                }

                if (addIterationsCount > 0) {
                    // Create more iterations if we will need them
                    while (this._freeIterations.length < addIterationsCount) {
                        this._freeIterations.push(this._createIteration());
                        if (logger.isDebug) {
                            newIterations.push(this._freeIterations[this._freeIterations.length-1]);
                        }
                    }
                    // Add iterations
                    var plusIterations = new Array(addIterationsCount);
                    for (i = reusableIterationsCount, j = 0; i < plusLength; i++, j++) {
                        var iteration = this._freeIterations.pop();
                        if (logger.isDebug) {
                            if(!newIterations.has(iteration)) {
                                logger.debug("Repetition:%s reusing %s", Object.hash(this), Object.hash(iteration));
                            }
                        }
                        var content = organizedContent[start + i];
                        iteration.object = content;

                        plusIterations[j] = iteration;
                    }
                    iterations.swap(index, 0, plusIterations);
                }

                if (removeIterationsCount > 0 || addIterationsCount > 0) {
                    // Update indexes for all subsequent iterations
                    this._updateIndexes(index);
                }
            }
        },

        /**
         * Used by handleOrganizedContentRangeChange to update the controller index
         * of every iteration following a change.
         * @private
         */
        _updateIndexes: {
            value: function (index) {
                var iterations = this.iterations;
                for (; index < iterations.length; index++) {
                    iterations[index].index = index;
                }
            }
        },

        _addDirtyClassListIteration: {
            value: function (iteration) {
                iteration.forEachElement(function (element) {
                    var component;
                    if (element && (component = element.component)) {
                        // If the element has a component then use the component's
                        // classList and let it handle drawing...
                        component.classList[iteration.active ? "add" : "remove"]("active");
                        component.classList[iteration.selected ? "add" : "remove"]("selected");
                        component.classList.remove("no-transition");
                    } else {
                        // ...otherwise we will handle the drawing of the classes
                        // on plain elements ourselves
                        this._dirtyClassListIterations.add(iteration);
                    }
                }, this);
            }
        },

        /**
         * @private
         */
        canDraw: {
            value: function () {
                // block for the usual component-related issues
                var canDraw = this.canDrawGate.value;

                // block until we have created enough (iterations to draw
                canDraw = canDraw && this._requestedIterations <= this._createdIterations;
                // block until we can draw initial content if we have not already
                canDraw = canDraw && (this._initialContentDrawn || this._canDrawInitialContent);

                // TODO: we're going to comment this out for now at least because
                // the repetition can get into a dead lock in the case of a nested
                // repetition (a repetition that has another repetition as direct
                // child component). It's possible to get into a state where the
                // inner repetition will never be able to draw unless the outer
                // repetition draws first. Hopefully the DrawManager will be able
                // to solve this. - @aadsm
                // block until all child components can draw
                //if (canDraw) {
                //    for (var i = 0; i < this.childComponents.length; i++) {
                //        var childComponent = this.childComponents[i];
                //        if (!childComponent.canDraw()) {
                //            canDraw = false;
                //        }
                //    }
                //}

                return canDraw;
            }
        },

        /**
         * An array of comment nodes that mark the boundaries between iterations on
         * the DOM.  When an iteration is retracted, the top boundary gets
         * retracted with it so the iteration at index N will always have boundary
         * N above it and N + 1 below it.  There must always be one more boundary
         * than there are iterations, representing the bottom boundary of the last
         * iteration.  That boundary gets added in first draw.
         * @private
         */
        _boundaries: {value: null},

        /**
         * A Set of iterations that have changed their CSS classes that are managed
         * by the repetition, "active", "selected", and "no-transition".
         * @private
         */
        _dirtyClassListIterations: {value: null},

        /**
         * The cumulative number of iterations that _createIteration has started
         * making.
         * @private
         */
        _requestedIterations: {value: null},

        /**
         * The cumulative number of iterations that _createIteration has finished
         * making.
         * @private
         */
        _createdIterations: {value: null},

        /**
         * In the first draw, the repetition gets rid of its innerHTML, which was
         * captured by the innerTemplate, and replaces it with the bottom boundary
         * marker comment.  This cannot be done until after the iteration template
         * is ready.
         *
         * This cycle may occur again if the innerTemplate is replaced.
         * @private
         */
        _canDrawInitialContent: {value: null},

        /**
         * Indicates that the first draw has come and gone and the repetition is
         * ready for business.
         * @private
         */
        _initialContentDrawn: {value: null},

        /**
         * @private
         */
        draw: {
            value: function () {
                var index;

                if (!this._initialContentDrawn) {
                    this._drawInitialContent();
                    this._initialContentDrawn = true;
                }

                // Synchronize iterations and _drawnIterations

                // Retract iterations that should no longer be visible
                for (index = this._drawnIterations.length - 1; index >= 0; index--) {
                    if (this._drawnIterations[index].index === null) {
                        this._drawnIterations[index].retractFromDocument();
                    }
                }

                // Inject iterations if they are not already in the right location
                for (index = 0; index < this.iterations.length; index++) {
                    var iteration = this.iterations[index];
                    if (iteration._drawnIndex !== iteration.index && iteration.isComponentTreeLoaded()) {
                        iteration.injectIntoDocument(index);
                    }
                }

                // Update class lists
                var iterations = this._dirtyClassListIterations.toArray();
                // Note that the iterations list must be cleared first because we
                // remove the "no-transition" class during the update if we find
                // it, which in turn schedules another draw and adds the iteration
                // back to the schedule.
                this._dirtyClassListIterations.clear();
                iterations.forEach(function (iteration) {
                    if(iteration.isComponentTreeLoaded()) {
                        iteration.forEachElement(function (element) {
                            // Only update classes that don't have a component, they
                            // are taken care of in _addDirtyClassListIteration
                            if (element.component) {
                                return;
                            }
                            element.classList[iteration.active ? "add" : "remove"]("active");
                            element.classList[iteration.selected ? "add" : "remove"]("selected");

                            // While we're at it, if the "no-transition" class has been
                            // added to this iteration, we will need to remove it in
                            // the next draw to allow the iteration to animate.
                            element.classList.remove("no-transition");
                        }, this);
                    }
                }, this);
            }
        },

        /**
         * @private
         */
        _drawInitialContent: {
            value: function () {
                var element = this.element;
                var childNodesCount = element.childNodes.length;
                for (var i = 0; i < childNodesCount; i++) {
                    element.removeChild(element.firstChild);
                }
                var bottomBoundary = element.ownerDocument.createTextNode("");
                element.appendChild(bottomBoundary);
                this._boundaries.push(bottomBoundary);
            }
        },

        /**
         * @private
         */
        // Used by the insertion and retraction operations to update the drawn
        // indexes of every iteration following a change.
        _updateDrawnIndexes: {
            value: function (drawnIndex) {
                var drawnIterations = this._drawnIterations;
                for (; drawnIndex < drawnIterations.length; drawnIndex++) {
                    drawnIterations[drawnIndex]._drawnIndex = drawnIndex;
                }
            }
        },

        // Selection Tracking
        // ------------------

        /**
         * If `isSelectionEnabled`, the repetition captures the pointer, preventing
         * it from passing to parent components, for example for the purpose of
         * scrolling.
         * @private
         */
        _selectionPointer: {value: null},

        /**
         * Original vertical coordinate of a touchstart/mousedown
         *
         * @type {number}
         * @private
         */
        _startX: { value: 0 },

        /**
         * Original horizontal coordinate of a touchstart/mousedown
         *
         * @type {number}
         * @private
         */
        _startY: { value: 0 },

        /**
         * Pointer to the current active Iteration
         *
         * @type {object}
         * @private
         */
        _currentActiveIteration: { value: null },

        /**
         * @private
         */
        // Called by constructor to monitor changes to isSelectionEnabled and arrange
        // the appropriate event listeners.
        handleIsSelectionEnabledChange: {
            value: function (selectionTracking) {
                if (selectionTracking) {
                    this._enableSelectionTracking();
                } else {
                    this._disableSelectionTracking();
                }
            }
        },

        /**
         * @private
         */
        // Called by handleIsSelectionEnabledChange in response to
        // isSelectionEnabled becoming true.
        _enableSelectionTracking: {
            value: function () {
                this._pressComposer.addEventListener("pressStart", this, false);
            }
        },

        /**
         * @private
         */
        // Called by handleIsSelectionEnabledChange in response to
        // isSelectionEnabled becoming false.
        _disableSelectionTracking: {
            value: function () {
                this._pressComposer.removeEventListener("pressStart", this, false);
            }
        },

        handlePressStart: {
            value: function (pressEvent) {
                var iteration = this._findIterationContainingElement(pressEvent.targetElement);

                if (iteration) {
                    this._startX = pressEvent.clientX;
                    this._startY = pressEvent.clientY;

                    this.__pressComposer.addEventListener("press", this, false);
                    this.__pressComposer.addEventListener("pressCancel", this, false);

                    iteration.shouldBecomeActive = true;
                    this._currentActiveIteration = iteration;
                }
            }
        },

        /**
         * @private
         */
        _ignoreSelection: {
            value: function () {
                if (this._currentActiveIteration) {
                    this._currentActiveIteration.shouldBecomeActive = false;
                    this._currentActiveIteration.active = false;
                    this._currentActiveIteration = null;
                }

                this.activeIterations.clear();

                this._startX = 0;
                this._startY = 0;

                this.__pressComposer.removeEventListener("press", this, false);
                this.__pressComposer.removeEventListener("pressCancel", this, false);
            }
        },

        /**
         * @private
         */
        handlePressCancel: {
            value: function () {
                this._ignoreSelection();
            }
        },

        handlePress: {
            value: function (event) {
                var iteration = this._findIterationContainingElement(event.targetElement);

                // And select it, if there is one
                if (iteration && this._currentActiveIteration === iteration) {
                    iteration.active = false;

                    if (!iteration.selected) {
                        iteration.selected = true;

                    } else if (this.allowsMultipleSelection) {
                        iteration.selected = false;
                    }
                }

                this._ignoreSelection();
            }
        },

        // ---

        /**
         * Finds the iteration that contains an element within the repetition.
         * This requires the repetition to maintain an index of all of the
         * <em>shallow</em> child elements of an iteration, _iterationForElement.
         * It does so in the Iteration.injectIntoDocument, but is only
         * approximately accurate since technically the child components of an
         * iteration may add and remove siblings after injection.  For the purpose
         * of selection, we caution the user to wrap any dynamic elements in a
         * static wrapper.
         * @private
         */
        _findIterationContainingElement: {
            value: function (element) {
                // Walk the document upward until we find the repetition and
                // a direct child of the repetition element.  The direct
                // child must be tracked by the repetition.
                var child;
                while (element) {
                    if (element === this.element) {
                        return this._iterationForElement.get(child);
                    }
                    child = element;
                    element = element.parentNode;
                }
            }
        },

        // Polymorphic helper types
        // ------------------------

        /**
         * The Iteration type for this repetition.  The repetition calls `new
         * this.Iteration()` to make new instances of iterations, so a child class
         * of `Repetition` may provide an alternate implementation of `Iteration`.
         * @private
         */
        Iteration: { value: Iteration, serializable: false }

    });